├── .fvmrc ├── .github └── file ├── .gitignore ├── .metadata ├── LICENSE ├── README.md ├── analysis_options.yaml ├── android ├── .gitignore ├── app │ ├── build.gradle │ ├── proguard-rules.pro │ └── src │ │ ├── debug │ │ └── AndroidManifest.xml │ │ ├── main │ │ ├── AndroidManifest.xml │ │ ├── kotlin │ │ │ └── org │ │ │ │ └── thingsboard │ │ │ │ └── app │ │ │ │ ├── KeepAliveService.kt │ │ │ │ ├── MainActivity.kt │ │ │ │ ├── TbWebAuthHandler.kt │ │ │ │ └── TbWebCallbackActivity.kt │ │ └── res │ │ │ ├── drawable-v21 │ │ │ └── launch_background.xml │ │ │ ├── drawable │ │ │ └── launch_background.xml │ │ │ ├── mipmap-hdpi │ │ │ ├── launcher_icon.png │ │ │ └── thingsboard.png │ │ │ ├── mipmap-mdpi │ │ │ ├── launcher_icon.png │ │ │ └── thingsboard.png │ │ │ ├── mipmap-xhdpi │ │ │ ├── launcher_icon.png │ │ │ └── thingsboard.png │ │ │ ├── mipmap-xxhdpi │ │ │ ├── launcher_icon.png │ │ │ └── thingsboard.png │ │ │ ├── mipmap-xxxhdpi │ │ │ ├── launcher_icon.png │ │ │ └── thingsboard.png │ │ │ ├── values-night │ │ │ └── styles.xml │ │ │ ├── values │ │ │ └── styles.xml │ │ │ └── xml │ │ │ ├── network_security_config.xml │ │ │ └── provider_paths.xml │ │ └── profile │ │ └── AndroidManifest.xml ├── build.gradle ├── gradle.properties ├── gradle │ └── wrapper │ │ └── gradle-wrapper.properties ├── settings.gradle └── settings_aar.gradle ├── assets └── images │ ├── apple-logo.svg │ ├── connect_mobile.svg │ ├── dashboard-placeholder.svg │ ├── device-not-connected.svg │ ├── device-profile-placeholder.svg │ ├── device_not_found.svg │ ├── facebook-logo.svg │ ├── github-logo.svg │ ├── google-logo.svg │ ├── mobile-connection-error.svg │ ├── no-data.svg │ ├── provisioning-done.svg │ ├── provisioning.svg │ ├── provisioning_error.svg │ ├── qr_code_scanner.svg │ ├── qr_code_scanner2.svg │ ├── thingsboard.png │ ├── thingsboard.svg │ ├── thingsboard_big_logo.svg │ ├── thingsboard_center.svg │ ├── thingsboard_outer.svg │ └── thingsboard_with_title.svg ├── ios ├── .gitignore ├── Flutter │ ├── AppFrameworkInfo.plist │ ├── Debug.xcconfig │ └── Release.xcconfig ├── Podfile ├── Podfile.lock ├── Runner.xcodeproj │ ├── project.pbxproj │ ├── project.xcworkspace │ │ ├── contents.xcworkspacedata │ │ └── xcshareddata │ │ │ ├── IDEWorkspaceChecks.plist │ │ │ └── WorkspaceSettings.xcsettings │ └── xcshareddata │ │ └── xcschemes │ │ └── Runner.xcscheme ├── Runner.xcworkspace │ ├── contents.xcworkspacedata │ └── xcshareddata │ │ ├── IDEWorkspaceChecks.plist │ │ └── WorkspaceSettings.xcsettings └── Runner │ ├── AppDelegate.swift │ ├── Assets.xcassets │ ├── AppIcon.appiconset │ │ ├── Contents.json │ │ ├── Icon-App-1024x1024@1x.png │ │ ├── Icon-App-20x20@1x.png │ │ ├── Icon-App-20x20@2x.png │ │ ├── Icon-App-20x20@3x.png │ │ ├── Icon-App-29x29@1x.png │ │ ├── Icon-App-29x29@2x.png │ │ ├── Icon-App-29x29@3x.png │ │ ├── Icon-App-40x40@1x.png │ │ ├── Icon-App-40x40@2x.png │ │ ├── Icon-App-40x40@3x.png │ │ ├── Icon-App-60x60@2x.png │ │ ├── Icon-App-60x60@3x.png │ │ ├── Icon-App-76x76@1x.png │ │ ├── Icon-App-76x76@2x.png │ │ └── Icon-App-83.5x83.5@2x.png │ └── LaunchImage.imageset │ │ ├── Contents.json │ │ ├── LaunchImage.png │ │ ├── LaunchImage@2x.png │ │ ├── LaunchImage@3x.png │ │ └── README.md │ ├── Base.lproj │ ├── LaunchScreen.storyboard │ └── Main.storyboard │ ├── Info-Debug.plist │ ├── Info-Release.plist │ ├── Runner-Bridging-Header.h │ ├── Runner.entitlements │ └── TbWebAuthHandler.swift ├── l10n.yaml ├── lib ├── app_bloc_observer.dart ├── config │ ├── routes │ │ └── router.dart │ └── themes │ │ └── tb_theme.dart ├── constants │ ├── app_constants.dart │ ├── assets_path.dart │ ├── database_keys.dart │ ├── enviroment_variables.dart │ └── hive_type_adapter_ids.dart ├── core │ ├── auth │ │ ├── auth_routes.dart │ │ ├── login │ │ │ ├── bloc │ │ │ │ ├── auth_bloc.dart │ │ │ │ ├── auth_events.dart │ │ │ │ ├── auth_states.dart │ │ │ │ └── bloc.dart │ │ │ ├── choose_region_screen.dart │ │ │ ├── login_page.dart │ │ │ ├── login_page_background.dart │ │ │ ├── region.dart │ │ │ ├── region.g.dart │ │ │ ├── region_widget.dart │ │ │ ├── reset_password_request_page.dart │ │ │ ├── select_region_screen.dart │ │ │ └── two_factor_authentication_page.dart │ │ ├── noauth │ │ │ ├── data │ │ │ │ ├── datasource │ │ │ │ │ └── remote │ │ │ │ │ │ ├── i_noauth_remote_datasource.dart │ │ │ │ │ │ └── noauth_remote_datasource.dart │ │ │ │ └── repository │ │ │ │ │ └── noauth_repository.dart │ │ │ ├── di │ │ │ │ └── noauth_di.dart │ │ │ ├── domain │ │ │ │ ├── repository │ │ │ │ │ └── i_noauth_repository.dart │ │ │ │ └── usecases │ │ │ │ │ └── switch_endpoint_usecase.dart │ │ │ ├── presentation │ │ │ │ ├── bloc │ │ │ │ │ ├── bloc.dart │ │ │ │ │ ├── noauth_bloc.dart │ │ │ │ │ ├── noauth_events.dart │ │ │ │ │ └── noauth_states.dart │ │ │ │ ├── view │ │ │ │ │ └── switch_endpoint_noauth_view.dart │ │ │ │ └── widgets │ │ │ │ │ ├── endpoint_name_widget.dart │ │ │ │ │ └── noauth_loading_widget.dart │ │ │ └── routes │ │ │ │ └── noauth_routes.dart │ │ ├── oauth2 │ │ │ ├── app_secret_provider.dart │ │ │ └── tb_oauth2_client.dart │ │ └── web │ │ │ └── tb_web_auth.dart │ ├── context │ │ ├── has_tb_context.dart │ │ ├── tb_context.dart │ │ └── tb_context_widget.dart │ ├── entity │ │ ├── entities_base.dart │ │ ├── entities_grid.dart │ │ ├── entities_list.dart │ │ ├── entities_list_widget.dart │ │ ├── entity_details_page.dart │ │ ├── entity_grid_card.dart │ │ └── entity_list_card.dart │ ├── init │ │ ├── init_app.dart │ │ ├── init_routes.dart │ │ └── inti_region_app.dart │ ├── logger │ │ ├── tb_log_output.dart │ │ ├── tb_logger.dart │ │ └── tb_logs_filter.dart │ └── usecases │ │ └── user_details_usecase.dart ├── firebase_options.dart ├── l10n │ ├── intl_ar.arb │ ├── intl_en.arb │ ├── intl_zh.arb │ ├── intl_zh_CN.arb │ └── intl_zh_TW.arb ├── locator.dart ├── main.dart ├── modules │ ├── alarm │ │ ├── alarm_routes.dart │ │ ├── alarms_base.dart │ │ ├── alarms_list.dart │ │ ├── data │ │ │ ├── datasource │ │ │ │ ├── alarm_types │ │ │ │ │ ├── alarm_types_datasource.dart │ │ │ │ │ └── i_alarm_types_datasource.dart │ │ │ │ ├── alarms │ │ │ │ │ ├── alarms_datasource.dart │ │ │ │ │ └── i_alarms_datasource.dart │ │ │ │ ├── assignee │ │ │ │ │ ├── assignee_datasource.dart │ │ │ │ │ └── i_assignee_datasource.dart │ │ │ │ └── details │ │ │ │ │ ├── alarm_details_datasource.dart │ │ │ │ │ └── i_alarm_details_datasource.dart │ │ │ └── repository │ │ │ │ ├── alarm_types │ │ │ │ └── alarm_types_repository.dart │ │ │ │ ├── alarms │ │ │ │ └── alarms_repository.dart │ │ │ │ ├── assignee │ │ │ │ └── assignee_repository.dart │ │ │ │ └── details │ │ │ │ └── alarm_details_repository.dart │ │ ├── di │ │ │ ├── alarm_details_di.dart │ │ │ ├── alarm_types_di.dart │ │ │ ├── alarms_di.dart │ │ │ └── assignee_di.dart │ │ ├── domain │ │ │ ├── entities │ │ │ │ ├── alarm_comment_entity.dart │ │ │ │ ├── alarm_filters_entity.dart │ │ │ │ ├── assignee_entity.dart │ │ │ │ └── filter_data_entity.dart │ │ │ ├── pagination │ │ │ │ ├── activity │ │ │ │ │ ├── alarm_activity_pagination_repository.dart │ │ │ │ │ └── alarm_activity_query_ctrl.dart │ │ │ │ ├── alarm_types │ │ │ │ │ ├── alarm_types_pagination_repository.dart │ │ │ │ │ └── alarm_types_query_ctrl.dart │ │ │ │ ├── alarms │ │ │ │ │ ├── alarms_pagination_repository.dart │ │ │ │ │ └── alarms_query_ctrl.dart │ │ │ │ └── assignee │ │ │ │ │ ├── alarm_assignee_pagiation_repository.dart │ │ │ │ │ ├── alarm_assignee_query_ctrl.dart │ │ │ │ │ ├── assignee_pagination_repository.dart │ │ │ │ │ └── assignee_query_ctrl.dart │ │ │ ├── repository │ │ │ │ ├── alarm_types │ │ │ │ │ └── i_alarm_types_repository.dart │ │ │ │ ├── alarms │ │ │ │ │ └── i_alarms_repository.dart │ │ │ │ ├── assignee │ │ │ │ │ └── i_assigne_repository.dart │ │ │ │ └── details │ │ │ │ │ └── i_alarm_details_repository.dart │ │ │ └── usecases │ │ │ │ ├── alarm_types │ │ │ │ └── fetch_alarm_types_usecase.dart │ │ │ │ ├── alarms │ │ │ │ ├── fetch_alarm_usecase.dart │ │ │ │ └── fetch_alarms_usecase.dart │ │ │ │ ├── assignee │ │ │ │ ├── assign_alarm_usecase.dart │ │ │ │ ├── fetch_alarm_assignee_usecase.dart │ │ │ │ ├── fetch_assignee_usecase.dart │ │ │ │ └── unassign_alarm_usecase.dart │ │ │ │ └── details │ │ │ │ ├── acknowledge_alarm_usecase.dart │ │ │ │ ├── clear_alarm_usecase.dart │ │ │ │ ├── delete_alarm_comment_usecase.dart │ │ │ │ ├── fetch_alarm_comments_usecase.dart │ │ │ │ └── post_alarm_comments_usecase.dart │ │ └── presentation │ │ │ ├── bloc │ │ │ ├── activity │ │ │ │ ├── alarm_activity_bloc.dart │ │ │ │ ├── alarm_activity_events.dart │ │ │ │ ├── alarm_activity_states.dart │ │ │ │ └── bloc.dart │ │ │ ├── alarm_assignee │ │ │ │ ├── alarm_assignee_bloc.dart │ │ │ │ ├── alarm_assignee_event.dart │ │ │ │ ├── alarm_assignee_state.dart │ │ │ │ └── bloc.dart │ │ │ ├── alarm_details │ │ │ │ ├── alarm_details_bloc.dart │ │ │ │ ├── alarm_details_events.dart │ │ │ │ ├── alarm_details_states.dart │ │ │ │ └── bloc.dart │ │ │ ├── alarm_types │ │ │ │ ├── alarm_types_bloc.dart │ │ │ │ ├── alarm_types_event.dart │ │ │ │ ├── alarm_types_state.dart │ │ │ │ └── bloc.dart │ │ │ ├── alarms_bloc.dart │ │ │ ├── alarms_events.dart │ │ │ ├── alarms_states.dart │ │ │ ├── assignee │ │ │ │ ├── assignee_bloc.dart │ │ │ │ ├── assignee_event.dart │ │ │ │ ├── assignee_state.dart │ │ │ │ └── bloc.dart │ │ │ ├── bloc.dart │ │ │ └── filters │ │ │ │ ├── alarm_filters_service.dart │ │ │ │ ├── filters │ │ │ │ ├── alarm_assignee_filter.dart │ │ │ │ ├── alarm_severity_filter.dart │ │ │ │ ├── alarm_status_filter.dart │ │ │ │ ├── alarm_type_filter.dart │ │ │ │ └── i_alarm_filter.dart │ │ │ │ └── i_alarm_filters_service.dart │ │ │ ├── view │ │ │ ├── alarm_details_page.dart │ │ │ ├── alarms_filter_page.dart │ │ │ ├── alarms_page.dart │ │ │ └── alarms_search_page.dart │ │ │ └── widgets │ │ │ ├── activity │ │ │ ├── activity_builder_widget.dart │ │ │ ├── alarm_activity_widget.dart │ │ │ ├── alarm_comment_textfield.dart │ │ │ ├── alarm_edit_comment_textfield.dart │ │ │ ├── system_activity_widget.dart │ │ │ └── user_comment_widget.dart │ │ │ ├── alarm_assignee │ │ │ ├── alarm_assignee_list_widget.dart │ │ │ └── alarm_assignee_widget.dart │ │ │ ├── alarm_control_filters_button.dart │ │ │ ├── alarm_filter_widget.dart │ │ │ ├── alarm_types │ │ │ ├── alarm_types_widget.dart │ │ │ └── types_list_widget.dart │ │ │ ├── alarms_card.dart │ │ │ ├── assignee │ │ │ ├── alarm_assignee_widget.dart │ │ │ ├── assignee_list_widget.dart │ │ │ ├── user_info_avatar_widget.dart │ │ │ └── user_info_widget.dart │ │ │ ├── details │ │ │ ├── alarm_cotroll_buttons.dart │ │ │ ├── alarm_details_content_widget.dart │ │ │ ├── alarm_details_widget.dart │ │ │ └── alarm_status_button.dart │ │ │ ├── filter_toggle_block_widget.dart │ │ │ └── tb_error_widget.dart │ ├── asset │ │ ├── asset_details_page.dart │ │ ├── asset_routes.dart │ │ ├── assets_base.dart │ │ ├── assets_list.dart │ │ ├── assets_list_widget.dart │ │ └── assets_page.dart │ ├── audit_log │ │ ├── audit_log_details_page.dart │ │ ├── audit_logs_base.dart │ │ ├── audit_logs_list.dart │ │ ├── audit_logs_page.dart │ │ └── audit_logs_routes.dart │ ├── customer │ │ ├── customer_details_page.dart │ │ ├── customer_routes.dart │ │ ├── customers_base.dart │ │ ├── customers_list.dart │ │ └── customers_page.dart │ ├── dashboard │ │ ├── dashboard_routes.dart │ │ ├── di │ │ │ └── dashboards_di.dart │ │ ├── domain │ │ │ ├── entites │ │ │ │ └── dashboard_arguments.dart │ │ │ ├── pagination │ │ │ │ ├── dashboards_pagination_repository.dart │ │ │ │ └── dashboards_query_ctrl.dart │ │ │ └── usecases │ │ │ │ └── fetch_dashboards_usecase.dart │ │ ├── main_dashboard_page.dart │ │ └── presentation │ │ │ ├── controller │ │ │ ├── dashboard_controller.dart │ │ │ └── dashboard_page_controller.dart │ │ │ ├── view │ │ │ ├── dashboards_page.dart │ │ │ ├── fullscreen_dashboard_page.dart │ │ │ ├── home_dashboard_page.dart │ │ │ └── single_dashboard_view.dart │ │ │ └── widgets │ │ │ ├── dashboard_grid_card.dart │ │ │ ├── dashboard_widget.dart │ │ │ ├── dashboards_appbar.dart │ │ │ └── dashboards_grid.dart │ ├── device │ │ ├── device_details_page.dart │ │ ├── device_profiles_base.dart │ │ ├── device_profiles_grid.dart │ │ ├── device_routes.dart │ │ ├── devices_base.dart │ │ ├── devices_list.dart │ │ ├── devices_list_page.dart │ │ ├── devices_list_widget.dart │ │ ├── devices_main_page.dart │ │ ├── devices_page.dart │ │ └── provisioning │ │ │ ├── ble │ │ │ ├── bloc │ │ │ │ ├── bloc.dart │ │ │ │ ├── eps_ble_provisioning_bloc.dart │ │ │ │ ├── events.dart │ │ │ │ └── states.dart │ │ │ ├── di │ │ │ │ └── esp_ble_di.dart │ │ │ └── view │ │ │ │ ├── ble_devices_empty_view.dart │ │ │ │ ├── ble_devices_view.dart │ │ │ │ ├── cannot_establish_session_view.dart │ │ │ │ ├── eps_ble_provisioning_view.dart │ │ │ │ └── esp_wifi_network_view.dart │ │ │ ├── bloc │ │ │ ├── bloc.dart │ │ │ ├── device_provisioning_bloc.dart │ │ │ ├── device_provisioning_events.dart │ │ │ └── device_provisioning_states.dart │ │ │ ├── route │ │ │ └── esp_provisioning_route.dart │ │ │ ├── soft_ap │ │ │ ├── bloc │ │ │ │ ├── bloc.dart │ │ │ │ ├── esp_softap_bloc.dart │ │ │ │ ├── esp_softap_events.dart │ │ │ │ └── esp_softap_states.dart │ │ │ ├── di │ │ │ │ └── esp_softap_di.dart │ │ │ └── view │ │ │ │ ├── esp_softap_error_view.dart │ │ │ │ ├── esp_softap_view.dart │ │ │ │ └── widgets │ │ │ │ ├── manually_connect_to_wifi.dart │ │ │ │ └── wifi_list.dart │ │ │ ├── view │ │ │ ├── device_provisioning_done.dart │ │ │ ├── device_provisioning_view.dart │ │ │ └── states │ │ │ │ ├── claiming_error.dart │ │ │ │ ├── claiming_wip.dart │ │ │ │ ├── confirming_wifi_connection.dart │ │ │ │ ├── connection_state_row.dart │ │ │ │ ├── manually_reconnect_to_wifi.dart │ │ │ │ ├── provision_error.dart │ │ │ │ ├── provision_states.dart │ │ │ │ ├── provision_success.dart │ │ │ │ └── sending_wifi_credentials.dart │ │ │ └── widgets │ │ │ ├── dotted_point_widget.dart │ │ │ ├── enter_password_dialog.dart │ │ │ ├── exit_confirmation_dialog.dart │ │ │ ├── help_message_widget.dart │ │ │ ├── return_to_dashboard_button.dart │ │ │ ├── scan_list_widget.dart │ │ │ └── try_again_button.dart │ ├── home │ │ ├── home_page.dart │ │ └── home_routes.dart │ ├── layout_pages │ │ └── bloc │ │ │ ├── bloc.dart │ │ │ ├── layout_pages_bloc.dart │ │ │ ├── layout_pages_event.dart │ │ │ └── layout_pages_state.dart │ ├── main │ │ ├── main_item_widget.dart │ │ ├── main_navigation_item.dart │ │ ├── main_page.dart │ │ ├── main_routes.dart │ │ └── tb_navigation_bar_widget.dart │ ├── more │ │ ├── more_menu_item_widget.dart │ │ ├── more_page.dart │ │ └── more_routes.dart │ ├── notification │ │ ├── controllers │ │ │ └── notification_query_ctrl.dart │ │ ├── notification_page.dart │ │ ├── repository │ │ │ ├── i_notification_query_repository.dart │ │ │ ├── notification_pagination_repository.dart │ │ │ └── notification_repository.dart │ │ ├── routes │ │ │ └── notification_routes.dart │ │ ├── service │ │ │ ├── i_notifications_local_service.dart │ │ │ └── notifications_local_service.dart │ │ └── widgets │ │ │ ├── filter_segmented_button.dart │ │ │ ├── no_notifications_found_widget.dart │ │ │ ├── notification_icon.dart │ │ │ ├── notification_list.dart │ │ │ ├── notification_slidable_widget.dart │ │ │ └── notification_widget.dart │ ├── profile │ │ ├── change_password_page.dart │ │ ├── profile_page.dart │ │ └── profile_routes.dart │ ├── tenant │ │ ├── tenant_details_page.dart │ │ ├── tenant_routes.dart │ │ ├── tenants_base.dart │ │ ├── tenants_list.dart │ │ ├── tenants_page.dart │ │ └── tenants_widget.dart │ ├── url │ │ ├── url_page.dart │ │ └── url_routes.dart │ └── version │ │ ├── bloc │ │ ├── bloc.dart │ │ ├── version_info_bloc.dart │ │ ├── version_info_events.dart │ │ └── version_info_states.dart │ │ ├── route │ │ ├── version_route.dart │ │ └── version_route_arguments.dart │ │ └── view │ │ ├── update_required_page.dart │ │ └── widgets │ │ ├── version_compare_widget.dart │ │ ├── version_empty_widget.dart │ │ └── version_single_widget.dart ├── thingsboard_app.dart ├── thingsboard_client.dart ├── utils │ ├── services │ │ ├── _tb_app_storage.dart │ │ ├── _tb_secure_storage.dart │ │ ├── _tb_web_local_storage.dart │ │ ├── communication │ │ │ ├── communication_service.dart │ │ │ ├── events.dart │ │ │ └── i_communication_service.dart │ │ ├── device_profile_cache.dart │ │ ├── endpoint │ │ │ ├── endpoint_service.dart │ │ │ └── i_endpoint_service.dart │ │ ├── entity_query_api.dart │ │ ├── firebase │ │ │ ├── firebase_service.dart │ │ │ └── i_firebase_service.dart │ │ ├── layouts │ │ │ ├── i_layout_service.dart │ │ │ └── layout_service.dart │ │ ├── local_database │ │ │ ├── i_local_database_service.dart │ │ │ └── local_database_service.dart │ │ ├── notification_service.dart │ │ ├── pagination_repository.dart │ │ ├── provisioning │ │ │ ├── eps_ble │ │ │ │ ├── i_wifi_provisioning_service.dart │ │ │ │ └── wifi_provisioning_service.dart │ │ │ ├── esp_smartconfig │ │ │ │ ├── esp_smartconfig_service.dart │ │ │ │ └── i_esp_smartconfig_service.dart │ │ │ └── soft_ap │ │ │ │ ├── i_soft_ap_service.dart │ │ │ │ └── soft_ap_service.dart │ │ ├── tb_app_storage.dart │ │ ├── user │ │ │ ├── i_user_service.dart │ │ │ └── user_service.dart │ │ └── widget_action_handler.dart │ ├── string_utils.dart │ ├── transition │ │ └── page_transitions.dart │ ├── ui │ │ ├── back_button_widget.dart │ │ ├── pagination_list_widget.dart │ │ ├── pagination_widgets │ │ │ ├── first_page_exception_widget.dart │ │ │ ├── first_page_progress_builder.dart │ │ │ ├── new_page_progress_builder.dart │ │ │ └── pagination_grid_widget.dart │ │ ├── qr_code_scanner.dart │ │ ├── tb_alert_dialog.dart │ │ ├── tb_text_styles.dart │ │ ├── text_extension.dart │ │ └── ui_utils.dart │ ├── ui_utils_routes.dart │ ├── usecase.dart │ └── utils.dart └── widgets │ ├── tb_app_bar.dart │ ├── tb_progress_indicator.dart │ ├── two_page_view.dart │ └── two_value_listenable_builder.dart ├── pubspec.lock ├── pubspec.yaml └── test ├── core └── noauth │ └── switch_endpoint_test.dart └── mocks.dart /.fvmrc: -------------------------------------------------------------------------------- 1 | { 2 | "flutter": "3.24.4" 3 | } -------------------------------------------------------------------------------- /.github/file: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Miscellaneous 2 | *.class 3 | *.log 4 | *.pyc 5 | *.swp 6 | .DS_Store 7 | .atom/ 8 | .buildlog/ 9 | .history 10 | .svn/ 11 | 12 | # IntelliJ related 13 | *.iml 14 | *.ipr 15 | *.iws 16 | .idea/ 17 | 18 | # The .vscode folder contains launch configuration and tasks you configure in 19 | # VS Code which you may wish to be included in version control, so this line 20 | # is commented out by default. 21 | #.vscode/ 22 | 23 | # Flutter/Dart/Pub related 24 | **/doc/api/ 25 | **/ios/Flutter/.last_build_id 26 | .dart_tool/ 27 | .flutter-plugins 28 | .flutter-plugins-dependencies 29 | .packages 30 | .pub-cache/ 31 | .pub/ 32 | /build/ 33 | 34 | # Web related 35 | lib/generated_plugin_registrant.dart 36 | 37 | # Symbolication related 38 | app.*.symbols 39 | 40 | # Obfuscation related 41 | app.*.map.json 42 | 43 | # Android Studio will place build artifacts here 44 | /android/app/debug 45 | /android/app/profile 46 | /android/app/release 47 | 48 | # FVM Version Cache 49 | .fvm/ -------------------------------------------------------------------------------- /.metadata: -------------------------------------------------------------------------------- 1 | # This file tracks properties of this Flutter project. 2 | # Used by Flutter tool to assess capabilities and perform upgrades etc. 3 | # 4 | # This file should be version controlled and should not be manually edited. 5 | 6 | version: 7 | revision: adc687823a831bbebe28bdccfac1a628ca621513 8 | channel: stable 9 | 10 | project_type: app 11 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright 2021 The ThingsBoard Authors 2 | 3 | Redistribution and use in source and binary forms, with or without modification, 4 | are permitted provided that the following conditions are met: 5 | 6 | * Redistributions of source code must retain the above copyright 7 | notice, this list of conditions and the following disclaimer. 8 | * Redistributions in binary form must reproduce the above 9 | copyright notice, this list of conditions and the following 10 | disclaimer in the documentation and/or other materials provided 11 | with the distribution. 12 | * Neither the name of Google Inc. nor the names of its 13 | contributors may be used to endorse or promote products derived 14 | from this software without specific prior written permission. 15 | 16 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 17 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 18 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 19 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR 20 | ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 21 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 22 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON 23 | ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 24 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 25 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 26 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## [ThingsBoard Mobile Application](https://thingsboard.io/products/mobile/) is an open-source project based on [Flutter](https://flutter.dev/) 2 | Powered by [ThingsBoard](https://thingsboard.io) IoT Platform 3 | 4 | Build your own IoT mobile application **with minimum coding efforts** 5 | 6 | ## Please be informed the Web platform is not supported, because it's a part of our main platform! 7 | 8 | ## Resources 9 | 10 | - [Getting started](https://thingsboard.io/docs/mobile/getting-started/) - learn how to set up and run your first IoT mobile app 11 | - [Customize your app](https://thingsboard.io/docs/mobile/customization/) - learn how to customize the app 12 | - [Publish your app](https://thingsboard.io/docs/mobile/release/) - learn how to publish app to Google Play or App Store 13 | 14 | ## Live demo app 15 | 16 | To be familiar with common app features try out our ThingsBoard Live mobile application available on Google Play and App Store 17 | - [Get it on Google Play](https://play.google.com/store/apps/details?id=org.thingsboard.demo.app&pcampaignid=pcampaignidMKT-Other-global-all-co-prtnr-py-PartBadge-Mar2515-1) 18 | - [Download on the App Store](https://apps.apple.com/us/app/thingsboard-live/id1594355695?itsct=apps_box_badge&itscg=30200) 19 | -------------------------------------------------------------------------------- /analysis_options.yaml: -------------------------------------------------------------------------------- 1 | # This file configures the analyzer, which statically analyzes Dart code to 2 | # check for errors, warnings, and lints. 3 | # 4 | # The issues identified by the analyzer are surfaced in the UI of Dart-enabled 5 | # IDEs (https://dart.dev/tools#ides-and-editors). The analyzer can also be 6 | # invoked from the command line by running `flutter analyze`. 7 | 8 | # The following line activates a set of recommended lints for Flutter apps, 9 | # packages, and plugins designed to encourage good coding practices. 10 | include: package:flutter_lints/flutter.yaml 11 | 12 | linter: 13 | # The lint rules applied to this project can be customized in the 14 | # section below to disable rules from the `package:flutter_lints/flutter.yaml` 15 | # included above or to enable additional rules. A list of all available lints 16 | # and their documentation is published at https://dart.dev/lints. 17 | # 18 | # Instead of disabling a lint rule for the entire project in the 19 | # section below, it can also be suppressed for a single line of code 20 | # or a specific dart file by using the `// ignore: name_of_lint` and 21 | # `// ignore_for_file: name_of_lint` syntax on the line or in the file 22 | # producing the lint. 23 | rules: 24 | require_trailing_commas: true 25 | prefer_single_quotes: true 26 | 27 | # Additional information about this file can be found at 28 | # https://dart.dev/guides/language/analysis-options 29 | -------------------------------------------------------------------------------- /android/.gitignore: -------------------------------------------------------------------------------- 1 | gradle-wrapper.jar 2 | /.gradle 3 | /captures/ 4 | /gradlew 5 | /gradlew.bat 6 | /local.properties 7 | GeneratedPluginRegistrant.java 8 | 9 | # Remember to never publicly share your keystore. 10 | # See https://flutter.dev/docs/deployment/android#reference-the-keystore-from-the-app 11 | key.properties 12 | -------------------------------------------------------------------------------- /android/app/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # The espressif library needs to have all their names and fields preserved. 2 | -keep,allowoptimization class espressif.* { *; } 3 | -------------------------------------------------------------------------------- /android/app/src/debug/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | -------------------------------------------------------------------------------- /android/app/src/main/kotlin/org/thingsboard/app/KeepAliveService.kt: -------------------------------------------------------------------------------- 1 | package org.thingsboard.app 2 | 3 | import android.app.Service 4 | import android.content.Intent 5 | import android.os.Binder 6 | import android.os.IBinder 7 | 8 | class KeepAliveService: Service() { 9 | companion object { 10 | val binder = Binder() 11 | } 12 | 13 | override fun onBind(intent: Intent): IBinder { 14 | return binder 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /android/app/src/main/kotlin/org/thingsboard/app/MainActivity.kt: -------------------------------------------------------------------------------- 1 | package org.thingsboard.app 2 | 3 | import androidx.annotation.NonNull 4 | import io.flutter.embedding.android.FlutterActivity 5 | import io.flutter.embedding.engine.FlutterEngine 6 | import io.flutter.plugin.common.MethodChannel 7 | 8 | class MainActivity: FlutterActivity() { 9 | 10 | override fun configureFlutterEngine(@NonNull flutterEngine: FlutterEngine) { 11 | super.configureFlutterEngine(flutterEngine) 12 | registerTbWebAuth(flutterEngine) 13 | } 14 | 15 | fun registerTbWebAuth(flutterEngine: FlutterEngine) { 16 | val channel = MethodChannel(flutterEngine.dartExecutor, "tb_web_auth") 17 | channel.setMethodCallHandler(TbWebAuthHandler(this)) 18 | } 19 | 20 | } 21 | -------------------------------------------------------------------------------- /android/app/src/main/kotlin/org/thingsboard/app/TbWebCallbackActivity.kt: -------------------------------------------------------------------------------- 1 | package org.thingsboard.app 2 | 3 | import android.app.Activity 4 | import android.net.Uri 5 | import android.os.Bundle 6 | 7 | class TbWebCallbackActivity: Activity() { 8 | override fun onCreate(savedInstanceState: Bundle?) { 9 | super.onCreate(savedInstanceState) 10 | 11 | val url = intent?.data 12 | val scheme = url?.scheme 13 | 14 | if (scheme != null) { 15 | TbWebAuthHandler.callbacks.remove(scheme)?.success(url.toString()) 16 | } 17 | 18 | finish() 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /android/app/src/main/res/drawable-v21/launch_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /android/app/src/main/res/drawable/launch_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-hdpi/launcher_icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thingsboard/flutter_thingsboard_app/ef5449e45711060f9944bf9213cb40c4dab6c311/android/app/src/main/res/mipmap-hdpi/launcher_icon.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-hdpi/thingsboard.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thingsboard/flutter_thingsboard_app/ef5449e45711060f9944bf9213cb40c4dab6c311/android/app/src/main/res/mipmap-hdpi/thingsboard.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-mdpi/launcher_icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thingsboard/flutter_thingsboard_app/ef5449e45711060f9944bf9213cb40c4dab6c311/android/app/src/main/res/mipmap-mdpi/launcher_icon.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-mdpi/thingsboard.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thingsboard/flutter_thingsboard_app/ef5449e45711060f9944bf9213cb40c4dab6c311/android/app/src/main/res/mipmap-mdpi/thingsboard.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xhdpi/launcher_icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thingsboard/flutter_thingsboard_app/ef5449e45711060f9944bf9213cb40c4dab6c311/android/app/src/main/res/mipmap-xhdpi/launcher_icon.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xhdpi/thingsboard.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thingsboard/flutter_thingsboard_app/ef5449e45711060f9944bf9213cb40c4dab6c311/android/app/src/main/res/mipmap-xhdpi/thingsboard.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xxhdpi/launcher_icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thingsboard/flutter_thingsboard_app/ef5449e45711060f9944bf9213cb40c4dab6c311/android/app/src/main/res/mipmap-xxhdpi/launcher_icon.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xxhdpi/thingsboard.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thingsboard/flutter_thingsboard_app/ef5449e45711060f9944bf9213cb40c4dab6c311/android/app/src/main/res/mipmap-xxhdpi/thingsboard.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xxxhdpi/launcher_icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thingsboard/flutter_thingsboard_app/ef5449e45711060f9944bf9213cb40c4dab6c311/android/app/src/main/res/mipmap-xxxhdpi/launcher_icon.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xxxhdpi/thingsboard.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thingsboard/flutter_thingsboard_app/ef5449e45711060f9944bf9213cb40c4dab6c311/android/app/src/main/res/mipmap-xxxhdpi/thingsboard.png -------------------------------------------------------------------------------- /android/app/src/main/res/values-night/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 9 | 15 | 19 | 20 | -------------------------------------------------------------------------------- /android/app/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 9 | 15 | 19 | 20 | -------------------------------------------------------------------------------- /android/app/src/main/res/xml/network_security_config.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /android/app/src/main/res/xml/provider_paths.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 6 | 7 | -------------------------------------------------------------------------------- /android/app/src/profile/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | -------------------------------------------------------------------------------- /android/build.gradle: -------------------------------------------------------------------------------- 1 | allprojects { 2 | repositories { 3 | google() 4 | jcenter() 5 | mavenCentral() 6 | maven { url ("https://jitpack.io/") } 7 | } 8 | } 9 | 10 | rootProject.buildDir = '../build' 11 | subprojects { 12 | project.buildDir = "${rootProject.buildDir}/${project.name}" 13 | } 14 | subprojects { 15 | afterEvaluate { project -> 16 | if (project.plugins.hasPlugin("com.android.application") || 17 | project.plugins.hasPlugin("com.android.library")) { 18 | project.android { 19 | compileSdkVersion 34 20 | buildToolsVersion "34.0.0" 21 | } 22 | } 23 | } 24 | } 25 | subprojects { 26 | project.evaluationDependsOn(':app') 27 | } 28 | 29 | tasks.register("clean", Delete) { 30 | delete rootProject.buildDir 31 | } 32 | -------------------------------------------------------------------------------- /android/gradle.properties: -------------------------------------------------------------------------------- 1 | org.gradle.jvmargs=-Xmx1536M 2 | android.useAndroidX=true 3 | android.enableJetifier=true 4 | -------------------------------------------------------------------------------- /android/gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Fri Jun 23 08:50:38 CEST 2017 2 | distributionBase=GRADLE_USER_HOME 3 | distributionPath=wrapper/dists 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | distributionUrl=https\://services.gradle.org/distributions/gradle-7.5.1-all.zip 7 | -------------------------------------------------------------------------------- /android/settings.gradle: -------------------------------------------------------------------------------- 1 | pluginManagement { 2 | def flutterSdkPath = { 3 | def properties = new Properties() 4 | file("local.properties").withInputStream { properties.load(it) } 5 | def flutterSdkPath = properties.getProperty("flutter.sdk") 6 | assert flutterSdkPath != null, "flutter.sdk not set in local.properties" 7 | return flutterSdkPath 8 | }() 9 | 10 | includeBuild("$flutterSdkPath/packages/flutter_tools/gradle") 11 | 12 | repositories { 13 | google() 14 | mavenCentral() 15 | gradlePluginPortal() 16 | } 17 | } 18 | 19 | plugins { 20 | id "dev.flutter.flutter-plugin-loader" version "1.0.0" 21 | id "com.android.application" version "7.2.2" apply false 22 | id "org.jetbrains.kotlin.android" version "1.9.0" apply false 23 | } 24 | 25 | include ":app" -------------------------------------------------------------------------------- /android/settings_aar.gradle: -------------------------------------------------------------------------------- 1 | include ':app' 2 | -------------------------------------------------------------------------------- /assets/images/apple-logo.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /assets/images/device-profile-placeholder.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /assets/images/facebook-logo.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /assets/images/github-logo.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /assets/images/google-logo.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /assets/images/qr_code_scanner.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /assets/images/thingsboard.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thingsboard/flutter_thingsboard_app/ef5449e45711060f9944bf9213cb40c4dab6c311/assets/images/thingsboard.png -------------------------------------------------------------------------------- /ios/.gitignore: -------------------------------------------------------------------------------- 1 | *.mode1v3 2 | *.mode2v3 3 | *.moved-aside 4 | *.pbxuser 5 | *.perspectivev3 6 | **/*sync/ 7 | .sconsign.dblite 8 | .tags* 9 | **/.vagrant/ 10 | **/DerivedData/ 11 | Icon? 12 | **/Pods/ 13 | **/.symlinks/ 14 | profile 15 | xcuserdata 16 | **/.generated/ 17 | Flutter/App.framework 18 | Flutter/Flutter.framework 19 | Flutter/Flutter.podspec 20 | Flutter/Generated.xcconfig 21 | Flutter/app.flx 22 | Flutter/app.zip 23 | Flutter/flutter_assets/ 24 | Flutter/flutter_export_environment.sh 25 | ServiceDefinitions.json 26 | Runner/GeneratedPluginRegistrant.* 27 | 28 | # Exceptions to above rules. 29 | !default.mode1v3 30 | !default.mode2v3 31 | !default.pbxuser 32 | !default.perspectivev3 33 | -------------------------------------------------------------------------------- /ios/Flutter/AppFrameworkInfo.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | App 9 | CFBundleIdentifier 10 | io.flutter.flutter.app 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | App 15 | CFBundlePackageType 16 | FMWK 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | 1.0 23 | MinimumOSVersion 24 | 12.0 25 | 26 | 27 | -------------------------------------------------------------------------------- /ios/Flutter/Debug.xcconfig: -------------------------------------------------------------------------------- 1 | #include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig" 2 | #include "Generated.xcconfig" 3 | -------------------------------------------------------------------------------- /ios/Flutter/Release.xcconfig: -------------------------------------------------------------------------------- 1 | #include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig" 2 | #include "Generated.xcconfig" 3 | -------------------------------------------------------------------------------- /ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | PreviewsEnabled 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /ios/Runner.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | PreviewsEnabled 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /ios/Runner/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | import UIKit 2 | import Flutter 3 | 4 | @main 5 | @objc class AppDelegate: FlutterAppDelegate { 6 | override func application( 7 | _ application: UIApplication, 8 | didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? 9 | ) -> Bool { 10 | 11 | self.registerTbWebAuth() 12 | 13 | if #available(iOS 10.0, *) { 14 | UNUserNotificationCenter.current().delegate = self as? UNUserNotificationCenterDelegate 15 | } 16 | 17 | GeneratedPluginRegistrant.register(with: self) 18 | return super.application(application, didFinishLaunchingWithOptions: launchOptions) 19 | } 20 | 21 | private func registerTbWebAuth() { 22 | let controller : FlutterViewController = window?.rootViewController as! FlutterViewController 23 | let channel = FlutterMethodChannel(name: "tb_web_auth", binaryMessenger: controller.binaryMessenger) 24 | let instance = TbWebAuthHandler() 25 | channel.setMethodCallHandler({ 26 | (call: FlutterMethodCall, result: @escaping FlutterResult) -> Void in 27 | instance.handle(call, result: result) 28 | }) 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thingsboard/flutter_thingsboard_app/ef5449e45711060f9944bf9213cb40c4dab6c311/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thingsboard/flutter_thingsboard_app/ef5449e45711060f9944bf9213cb40c4dab6c311/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thingsboard/flutter_thingsboard_app/ef5449e45711060f9944bf9213cb40c4dab6c311/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thingsboard/flutter_thingsboard_app/ef5449e45711060f9944bf9213cb40c4dab6c311/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thingsboard/flutter_thingsboard_app/ef5449e45711060f9944bf9213cb40c4dab6c311/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thingsboard/flutter_thingsboard_app/ef5449e45711060f9944bf9213cb40c4dab6c311/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thingsboard/flutter_thingsboard_app/ef5449e45711060f9944bf9213cb40c4dab6c311/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thingsboard/flutter_thingsboard_app/ef5449e45711060f9944bf9213cb40c4dab6c311/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thingsboard/flutter_thingsboard_app/ef5449e45711060f9944bf9213cb40c4dab6c311/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thingsboard/flutter_thingsboard_app/ef5449e45711060f9944bf9213cb40c4dab6c311/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thingsboard/flutter_thingsboard_app/ef5449e45711060f9944bf9213cb40c4dab6c311/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thingsboard/flutter_thingsboard_app/ef5449e45711060f9944bf9213cb40c4dab6c311/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thingsboard/flutter_thingsboard_app/ef5449e45711060f9944bf9213cb40c4dab6c311/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thingsboard/flutter_thingsboard_app/ef5449e45711060f9944bf9213cb40c4dab6c311/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thingsboard/flutter_thingsboard_app/ef5449e45711060f9944bf9213cb40c4dab6c311/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "LaunchImage.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "filename" : "LaunchImage@2x.png", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "idiom" : "universal", 15 | "filename" : "LaunchImage@3x.png", 16 | "scale" : "3x" 17 | } 18 | ], 19 | "info" : { 20 | "version" : 1, 21 | "author" : "xcode" 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thingsboard/flutter_thingsboard_app/ef5449e45711060f9944bf9213cb40c4dab6c311/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thingsboard/flutter_thingsboard_app/ef5449e45711060f9944bf9213cb40c4dab6c311/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thingsboard/flutter_thingsboard_app/ef5449e45711060f9944bf9213cb40c4dab6c311/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md: -------------------------------------------------------------------------------- 1 | # Launch Screen Assets 2 | 3 | You can customize the launch screen with your own desired assets by replacing the image files in this directory. 4 | 5 | You can also do it by opening your Flutter project's Xcode project with `open ios/Runner.xcworkspace`, selecting `Runner/Assets.xcassets` in the Project Navigator and dropping in the desired images. -------------------------------------------------------------------------------- /ios/Runner/Runner-Bridging-Header.h: -------------------------------------------------------------------------------- 1 | #import "GeneratedPluginRegistrant.h" 2 | -------------------------------------------------------------------------------- /ios/Runner/Runner.entitlements: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | aps-environment 6 | development 7 | com.apple.developer.associated-domains 8 | 9 | applinks:demo.thingsboard.io 10 | 11 | com.apple.developer.networking.HotspotConfiguration 12 | 13 | com.apple.developer.networking.wifi-info 14 | 15 | com.apple.security.network.client 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /l10n.yaml: -------------------------------------------------------------------------------- 1 | arb-dir: lib/l10n 2 | template-arb-file: intl_en.arb 3 | output-localization-file: messages.dart 4 | output-class: S 5 | preferred-supported-locales: [ en ] 6 | nullable-getter: false 7 | -------------------------------------------------------------------------------- /lib/app_bloc_observer.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter_bloc/flutter_bloc.dart'; 2 | import 'package:thingsboard_app/core/logger/tb_logger.dart'; 3 | 4 | class AppBlocObserver extends BlocObserver { 5 | const AppBlocObserver(this.logger); 6 | 7 | final TbLogger logger; 8 | 9 | @override 10 | void onCreate(BlocBase bloc) { 11 | super.onCreate(bloc); 12 | logger.info('AppBlocObserver::onCreate(${bloc.runtimeType})'); 13 | } 14 | 15 | @override 16 | void onEvent(Bloc bloc, Object? event) { 17 | super.onEvent(bloc, event); 18 | logger.info('AppBlocObserver::onEvent(${bloc.runtimeType}, $event)'); 19 | } 20 | 21 | @override 22 | void onError(BlocBase bloc, Object error, StackTrace stackTrace) { 23 | super.onError(bloc, error, stackTrace); 24 | logger.info( 25 | 'AppBlocObserver::onError(${bloc.runtimeType}, $error, $stackTrace)', 26 | ); 27 | } 28 | 29 | @override 30 | void onClose(BlocBase bloc) { 31 | super.onClose(bloc); 32 | logger.info('AppBlocObserver::onClose(${bloc.runtimeType})'); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /lib/constants/app_constants.dart: -------------------------------------------------------------------------------- 1 | abstract class ThingsboardAppConstants { 2 | static const thingsBoardApiEndpoint = 'http://localhost:8080'; 3 | static const thingsboardOAuth2CallbackUrlScheme = 'org.thingsboard.app.auth'; 4 | 5 | static const thingsboardIOSAppSecret = 'Your app secret here'; 6 | static const thingsboardAndroidAppSecret = 'Your app secret here'; 7 | } 8 | -------------------------------------------------------------------------------- /lib/constants/database_keys.dart: -------------------------------------------------------------------------------- 1 | abstract final class DatabaseKeys { 2 | static const thingsBoardApiEndpointKey = 'thingsBoardApiEndpoint'; 3 | static const initialAppLink = 'initialAppLink'; 4 | static const selectedRegion = 'selectedRegion'; 5 | } 6 | -------------------------------------------------------------------------------- /lib/constants/enviroment_variables.dart: -------------------------------------------------------------------------------- 1 | abstract final class EnvironmentVariables { 2 | static const apiCalls = 3 | bool.fromEnvironment('API_CALLS', defaultValue: false); 4 | static const verbose = bool.fromEnvironment('VERBOSE', defaultValue: false); 5 | } 6 | -------------------------------------------------------------------------------- /lib/constants/hive_type_adapter_ids.dart: -------------------------------------------------------------------------------- 1 | abstract final class HiveTypeAdapterIds { 2 | static const regionAdapterId = 1; 3 | } 4 | -------------------------------------------------------------------------------- /lib/core/auth/login/bloc/auth_events.dart: -------------------------------------------------------------------------------- 1 | import 'package:equatable/equatable.dart'; 2 | import 'package:thingsboard_app/thingsboard_client.dart' show PlatformType; 3 | 4 | sealed class AuthEvent extends Equatable { 5 | const AuthEvent(); 6 | 7 | @override 8 | List get props => []; 9 | } 10 | 11 | final class AuthFetchEvent extends AuthEvent { 12 | const AuthFetchEvent({ 13 | required this.packageName, 14 | required this.platformType, 15 | }); 16 | 17 | final String packageName; 18 | final PlatformType platformType; 19 | 20 | @override 21 | List get props => [packageName, platformType]; 22 | } 23 | -------------------------------------------------------------------------------- /lib/core/auth/login/bloc/auth_states.dart: -------------------------------------------------------------------------------- 1 | import 'package:equatable/equatable.dart'; 2 | import 'package:thingsboard_app/thingsboard_client.dart' show OAuth2ClientInfo; 3 | 4 | sealed class AuthState extends Equatable { 5 | const AuthState(); 6 | 7 | @override 8 | List get props => []; 9 | } 10 | 11 | final class AuthLoadingState extends AuthState { 12 | const AuthLoadingState(); 13 | } 14 | 15 | final class AuthDataState extends AuthState { 16 | const AuthDataState({ 17 | required this.oAuthClients, 18 | }); 19 | 20 | final List oAuthClients; 21 | 22 | @override 23 | List get props => [oAuthClients]; 24 | } 25 | -------------------------------------------------------------------------------- /lib/core/auth/login/bloc/bloc.dart: -------------------------------------------------------------------------------- 1 | export 'auth_bloc.dart'; 2 | export 'auth_events.dart'; 3 | export 'auth_states.dart'; 4 | -------------------------------------------------------------------------------- /lib/core/auth/login/login_page_background.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | class LoginPageBackground extends StatelessWidget { 4 | const LoginPageBackground({super.key}); 5 | 6 | @override 7 | Widget build(BuildContext context) { 8 | return SizedBox.expand( 9 | child: CustomPaint( 10 | painter: _LoginPageBackgroundPainter( 11 | color: Theme.of(context).primaryColor, 12 | ), 13 | ), 14 | ); 15 | } 16 | } 17 | 18 | class _LoginPageBackgroundPainter extends CustomPainter { 19 | final Color color; 20 | 21 | const _LoginPageBackgroundPainter({required this.color}); 22 | 23 | @override 24 | void paint(Canvas canvas, Size size) { 25 | final paint = Paint()..color = color.withAlpha(14); 26 | paint.style = PaintingStyle.fill; 27 | var topPath = Path(); 28 | topPath.moveTo(0, 0); 29 | topPath.lineTo(size.width / 2, 0); 30 | topPath.lineTo(0, size.height / 10); 31 | topPath.close(); 32 | canvas.drawPath(topPath, paint); 33 | var bottomPath = Path(); 34 | bottomPath.moveTo(0, size.height * 0.98); 35 | bottomPath.lineTo(size.width, size.height * 0.78); 36 | bottomPath.lineTo(size.width, size.height); 37 | bottomPath.lineTo(0, size.height); 38 | bottomPath.close(); 39 | canvas.drawPath(bottomPath, paint); 40 | } 41 | 42 | @override 43 | bool shouldRepaint(covariant _LoginPageBackgroundPainter oldDelegate) { 44 | return color != oldDelegate.color; 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /lib/core/auth/login/region.dart: -------------------------------------------------------------------------------- 1 | import 'package:hive/hive.dart'; 2 | import 'package:thingsboard_app/constants/hive_type_adapter_ids.dart'; 3 | 4 | part 'region.g.dart'; 5 | 6 | @HiveType(typeId: HiveTypeAdapterIds.regionAdapterId) 7 | enum Region { 8 | @HiveField(0) 9 | northAmerica, 10 | @HiveField(1) 11 | europe, 12 | @HiveField(2) 13 | custom 14 | } 15 | 16 | extension RegionToString on Region { 17 | String regionToString() { 18 | switch (this) { 19 | case Region.northAmerica: 20 | return 'North America'; 21 | case Region.europe: 22 | return 'Europe'; 23 | case Region.custom: 24 | return 'Regions'; 25 | } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /lib/core/auth/login/region.g.dart: -------------------------------------------------------------------------------- 1 | // GENERATED CODE - DO NOT MODIFY BY HAND 2 | 3 | part of 'region.dart'; 4 | 5 | // ************************************************************************** 6 | // TypeAdapterGenerator 7 | // ************************************************************************** 8 | 9 | class RegionAdapter extends TypeAdapter { 10 | @override 11 | final int typeId = 1; 12 | 13 | @override 14 | Region read(BinaryReader reader) { 15 | switch (reader.readByte()) { 16 | case 0: 17 | return Region.northAmerica; 18 | case 1: 19 | return Region.europe; 20 | case 2: 21 | return Region.custom; 22 | default: 23 | return Region.northAmerica; 24 | } 25 | } 26 | 27 | @override 28 | void write(BinaryWriter writer, Region obj) { 29 | switch (obj) { 30 | case Region.northAmerica: 31 | writer.writeByte(0); 32 | break; 33 | case Region.europe: 34 | writer.writeByte(1); 35 | break; 36 | case Region.custom: 37 | writer.writeByte(2); 38 | break; 39 | } 40 | } 41 | 42 | @override 43 | int get hashCode => typeId.hashCode; 44 | 45 | @override 46 | bool operator ==(Object other) => 47 | identical(this, other) || 48 | other is RegionAdapter && 49 | runtimeType == other.runtimeType && 50 | typeId == other.typeId; 51 | } 52 | -------------------------------------------------------------------------------- /lib/core/auth/noauth/data/datasource/remote/i_noauth_remote_datasource.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/foundation.dart'; 2 | import 'package:thingsboard_app/thingsboard_client.dart'; 3 | 4 | abstract interface class INoAuthRemoteDatasource { 5 | Future getJwtToken({ 6 | required String host, 7 | required String key, 8 | }); 9 | 10 | Future setUserFromJwtToken(LoginResponse loginData); 11 | 12 | Future logout({RequestConfig? requestConfig, bool notifyUser = true}); 13 | 14 | Future reInit({ 15 | required String endpoint, 16 | required VoidCallback onDone, 17 | required ErrorCallback onError, 18 | }); 19 | 20 | bool isAuthenticated(); 21 | 22 | AuthUser getAuthUserFromJwt(String jwt); 23 | 24 | AuthUser? getCurrentlyAuthenticatedUserOrNull(); 25 | } 26 | -------------------------------------------------------------------------------- /lib/core/auth/noauth/domain/repository/i_noauth_repository.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/foundation.dart'; 2 | import 'package:thingsboard_app/thingsboard_client.dart'; 3 | 4 | abstract interface class INoAuthRepository { 5 | Future getJwtToken({ 6 | required String host, 7 | required String key, 8 | }); 9 | 10 | Future setUserFromJwtToken(LoginResponse loginData); 11 | 12 | Future logout({RequestConfig? requestConfig, bool notifyUser = true}); 13 | 14 | Future reInit({ 15 | required String endpoint, 16 | required VoidCallback onDone, 17 | required ErrorCallback onError, 18 | }); 19 | 20 | bool isAuthenticated(); 21 | 22 | AuthUser getAuthUserFromJwt(String jwt); 23 | 24 | AuthUser? getCurrentlyAuthenticatedUserOrNull(); 25 | } 26 | -------------------------------------------------------------------------------- /lib/core/auth/noauth/presentation/bloc/bloc.dart: -------------------------------------------------------------------------------- 1 | export 'noauth_bloc.dart'; 2 | export 'noauth_events.dart'; 3 | export 'noauth_states.dart'; 4 | -------------------------------------------------------------------------------- /lib/core/auth/noauth/presentation/bloc/noauth_events.dart: -------------------------------------------------------------------------------- 1 | import 'package:equatable/equatable.dart'; 2 | 3 | sealed class NoAuthEvent extends Equatable { 4 | const NoAuthEvent(); 5 | 6 | @override 7 | List get props => []; 8 | } 9 | 10 | final class SwitchToAnotherEndpointEvent extends NoAuthEvent { 11 | const SwitchToAnotherEndpointEvent({required this.parameters}); 12 | 13 | final Map? parameters; 14 | 15 | @override 16 | List get props => [parameters]; 17 | } 18 | 19 | final class SwitchEndpointProgressUpdateEvent extends NoAuthEvent { 20 | const SwitchEndpointProgressUpdateEvent({required this.progressMessage}); 21 | 22 | final String progressMessage; 23 | 24 | @override 25 | List get props => [progressMessage]; 26 | } 27 | 28 | final class SwitchEndpointDoneEvent extends NoAuthEvent { 29 | const SwitchEndpointDoneEvent(); 30 | } 31 | 32 | final class SwitchEndpointErrorEvent extends NoAuthEvent { 33 | const SwitchEndpointErrorEvent({required this.message}); 34 | 35 | final String? message; 36 | 37 | @override 38 | List get props => [message]; 39 | } 40 | -------------------------------------------------------------------------------- /lib/core/auth/noauth/presentation/bloc/noauth_states.dart: -------------------------------------------------------------------------------- 1 | import 'package:equatable/equatable.dart'; 2 | 3 | sealed class NoAuthState extends Equatable { 4 | const NoAuthState(); 5 | 6 | @override 7 | List get props => []; 8 | } 9 | 10 | final class NoAuthLoadingState extends NoAuthState { 11 | const NoAuthLoadingState(); 12 | } 13 | 14 | final class NoAuthWipState extends NoAuthState { 15 | const NoAuthWipState({required this.currentStateMessage}); 16 | 17 | final String currentStateMessage; 18 | 19 | @override 20 | List get props => [currentStateMessage]; 21 | } 22 | 23 | final class NoAuthErrorState extends NoAuthState { 24 | const NoAuthErrorState({required this.message}); 25 | 26 | final String? message; 27 | 28 | @override 29 | List get props => [message]; 30 | } 31 | 32 | final class NoAuthDoneState extends NoAuthState { 33 | const NoAuthDoneState(); 34 | } 35 | -------------------------------------------------------------------------------- /lib/core/auth/noauth/presentation/widgets/endpoint_name_widget.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:thingsboard_app/utils/ui/tb_text_styles.dart'; 3 | 4 | class EndpointNameWidget extends StatelessWidget { 5 | const EndpointNameWidget({required this.endpoint, super.key}); 6 | 7 | final String endpoint; 8 | 9 | @override 10 | Widget build(BuildContext context) { 11 | return Container( 12 | decoration: BoxDecoration( 13 | borderRadius: BorderRadius.circular(4), 14 | color: Theme.of(context).primaryColor.withOpacity(.06), 15 | ), 16 | padding: const EdgeInsets.symmetric(vertical: 6, horizontal: 8), 17 | child: Center( 18 | child: Text( 19 | Uri.parse(endpoint).host, 20 | style: TbTextStyles.bodySmall.copyWith( 21 | color: Theme.of(context).primaryColor, 22 | ), 23 | ), 24 | ), 25 | ); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /lib/core/auth/noauth/presentation/widgets/noauth_loading_widget.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:thingsboard_app/widgets/tb_progress_indicator.dart'; 3 | 4 | class NoAuthLoadingWidget extends StatelessWidget { 5 | const NoAuthLoadingWidget({super.key}); 6 | 7 | @override 8 | Widget build(BuildContext context) { 9 | return SizedBox.expand( 10 | child: Container( 11 | color: const Color(0x99FFFFFF), 12 | child: const Center( 13 | child: TbProgressIndicator(size: 50.0), 14 | ), 15 | ), 16 | ); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /lib/core/auth/noauth/routes/noauth_routes.dart: -------------------------------------------------------------------------------- 1 | import 'package:fluro/fluro.dart'; 2 | import 'package:thingsboard_app/config/routes/router.dart'; 3 | import 'package:thingsboard_app/core/auth/noauth/presentation/view/switch_endpoint_noauth_view.dart'; 4 | 5 | class NoAuthRoutes extends TbRoutes { 6 | NoAuthRoutes(super.tbContext); 7 | 8 | static const noAuthPageRoutes = '/api/noauth/qr'; 9 | 10 | late final noAuthQrHandler = Handler( 11 | handlerFunc: (context, params) { 12 | return SwitchEndpointNoAuthView( 13 | tbContext: tbContext, 14 | arguments: context?.settings?.arguments as Map?, 15 | ); 16 | }, 17 | ); 18 | 19 | @override 20 | void doRegisterRoutes(FluroRouter router) { 21 | router.define(noAuthPageRoutes, handler: noAuthQrHandler); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /lib/core/auth/oauth2/app_secret_provider.dart: -------------------------------------------------------------------------------- 1 | import 'package:thingsboard_app/constants/app_constants.dart'; 2 | import 'package:thingsboard_app/thingsboard_client.dart' show PlatformType; 3 | 4 | abstract class AppSecretProvider { 5 | Future getAppSecret(PlatformType platformType); 6 | 7 | factory AppSecretProvider.local() => _LocalAppSecretProvider(); 8 | } 9 | 10 | /// Not for production (only for debugging) 11 | class _LocalAppSecretProvider implements AppSecretProvider { 12 | @override 13 | Future getAppSecret(PlatformType platformType) async { 14 | if (platformType == PlatformType.IOS) { 15 | return ThingsboardAppConstants.thingsboardIOSAppSecret; 16 | } else if (platformType == PlatformType.ANDROID) { 17 | return ThingsboardAppConstants.thingsboardAndroidAppSecret; 18 | } 19 | 20 | throw UnsupportedError('This platform is not supported $platformType'); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /lib/core/auth/web/tb_web_auth.dart: -------------------------------------------------------------------------------- 1 | import 'dart:async'; 2 | 3 | import 'package:flutter/cupertino.dart'; 4 | import 'package:flutter/services.dart' show MethodChannel; 5 | 6 | class _OnAppLifecycleResumeObserver extends WidgetsBindingObserver { 7 | final Function onResumed; 8 | 9 | _OnAppLifecycleResumeObserver(this.onResumed); 10 | 11 | @override 12 | void didChangeAppLifecycleState(AppLifecycleState state) { 13 | if (state == AppLifecycleState.resumed) { 14 | onResumed(); 15 | } 16 | } 17 | } 18 | 19 | class TbWebAuth { 20 | static const MethodChannel _channel = MethodChannel('tb_web_auth'); 21 | 22 | static final _OnAppLifecycleResumeObserver _resumedObserver = 23 | _OnAppLifecycleResumeObserver(() { 24 | _cleanUpDanglingCalls(); 25 | }); 26 | 27 | static Future authenticate({ 28 | required String url, 29 | required String callbackUrlScheme, 30 | bool? saveHistory, 31 | }) async { 32 | WidgetsBinding.instance.removeObserver( 33 | _resumedObserver, 34 | ); // safety measure so we never add this observer twice 35 | WidgetsBinding.instance.addObserver(_resumedObserver); 36 | return await _channel.invokeMethod('authenticate', { 37 | 'url': url, 38 | 'callbackUrlScheme': callbackUrlScheme, 39 | 'saveHistory': saveHistory, 40 | }) as String; 41 | } 42 | 43 | static Future _cleanUpDanglingCalls() async { 44 | await _channel.invokeMethod('cleanUpDanglingCalls'); 45 | WidgetsBinding.instance.removeObserver(_resumedObserver); 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /lib/core/init/init_app.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:thingsboard_app/core/context/tb_context.dart'; 3 | import 'package:thingsboard_app/core/context/tb_context_widget.dart'; 4 | import 'package:thingsboard_app/widgets/tb_progress_indicator.dart'; 5 | 6 | class ThingsboardInitApp extends TbPageWidget { 7 | ThingsboardInitApp(TbContext tbContext, {Key? key}) 8 | : super(tbContext, key: key); 9 | 10 | @override 11 | State createState() => _ThingsboardInitAppState(); 12 | } 13 | 14 | class _ThingsboardInitAppState extends TbPageState { 15 | @override 16 | void initState() { 17 | super.initState(); 18 | initTbContext(); 19 | } 20 | 21 | @override 22 | Widget build(BuildContext context) { 23 | return Container( 24 | alignment: Alignment.center, 25 | color: Colors.white, 26 | child: const TbProgressIndicator(size: 50.0), 27 | ); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /lib/core/init/init_routes.dart: -------------------------------------------------------------------------------- 1 | import 'package:fluro/fluro.dart'; 2 | import 'package:flutter/material.dart'; 3 | import 'package:flutter/widgets.dart'; 4 | import 'package:thingsboard_app/config/routes/router.dart'; 5 | import 'package:thingsboard_app/core/context/tb_context.dart'; 6 | import 'package:thingsboard_app/core/init/inti_region_app.dart'; 7 | 8 | import 'init_app.dart'; 9 | 10 | class InitRoutes extends TbRoutes { 11 | InitRoutes(TbContext tbContext) : super(tbContext); 12 | 13 | late final initHandler = Handler( 14 | handlerFunc: (BuildContext? context, Map params) { 15 | return ThingsboardInitApp(tbContext); 16 | }, 17 | ); 18 | 19 | late final regionSelectedHandler = Handler( 20 | handlerFunc: (BuildContext? context, Map params) { 21 | return ThingsboardInitRegionApp(tbContext); 22 | }, 23 | ); 24 | 25 | @override 26 | void doRegisterRoutes(router) { 27 | router.define('/', handler: initHandler); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /lib/core/logger/tb_log_output.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/foundation.dart'; 2 | import 'package:logger/logger.dart'; 3 | 4 | class TbLogOutput extends LogOutput { 5 | @override 6 | void output(OutputEvent event) { 7 | for (final line in event.lines) { 8 | debugPrint(line); 9 | } 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /lib/core/logger/tb_logger.dart: -------------------------------------------------------------------------------- 1 | import 'package:logger/logger.dart'; 2 | import 'package:thingsboard_app/core/logger/tb_log_output.dart'; 3 | import 'package:thingsboard_app/core/logger/tb_logs_filter.dart'; 4 | 5 | class TbLogger { 6 | final _logger = Logger( 7 | filter: TbLogsFilter(), 8 | printer: PrefixPrinter( 9 | PrettyPrinter( 10 | methodCount: 0, 11 | errorMethodCount: 8, 12 | lineLength: 200, 13 | colors: false, 14 | printEmojis: true, 15 | printTime: false, 16 | ), 17 | ), 18 | output: TbLogOutput(), 19 | ); 20 | 21 | void trace(dynamic message, [dynamic error, StackTrace? stackTrace]) { 22 | _logger.t(message, error: error, stackTrace: stackTrace); 23 | } 24 | 25 | void debug(dynamic message, [dynamic error, StackTrace? stackTrace]) { 26 | _logger.d(message, error: error, stackTrace: stackTrace); 27 | } 28 | 29 | void info(dynamic message, [dynamic error, StackTrace? stackTrace]) { 30 | _logger.i(message, error: error, stackTrace: stackTrace); 31 | } 32 | 33 | void warn(dynamic message, [dynamic error, StackTrace? stackTrace]) { 34 | _logger.w(message, error: error, stackTrace: stackTrace); 35 | } 36 | 37 | void error(dynamic message, [dynamic error, StackTrace? stackTrace]) { 38 | _logger.e(message, error: error, stackTrace: stackTrace); 39 | } 40 | 41 | void fatal(dynamic message, [dynamic error, StackTrace? stackTrace]) { 42 | _logger.f(message, error: error, stackTrace: stackTrace); 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /lib/core/logger/tb_logs_filter.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/foundation.dart'; 2 | import 'package:logger/logger.dart'; 3 | import 'package:thingsboard_app/constants/enviroment_variables.dart'; 4 | 5 | class TbLogsFilter extends LogFilter { 6 | @override 7 | bool shouldLog(LogEvent event) { 8 | if (EnvironmentVariables.verbose) { 9 | return true; 10 | } else if (kReleaseMode) { 11 | return event.level.index >= Level.warning.index; 12 | } else { 13 | return true; 14 | } 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /lib/core/usecases/user_details_usecase.dart: -------------------------------------------------------------------------------- 1 | import 'package:thingsboard_app/utils/usecase.dart'; 2 | 3 | class UserDetailsUseCase extends UseCase { 4 | const UserDetailsUseCase(); 5 | 6 | @override 7 | UserDetailsOutput call(UserDetailsParams params) { 8 | final name = '${params.firstName ?? ''} ${params.lastName ?? ''}'; 9 | final displayName = name.length > 1 ? name : params.email; 10 | 11 | String shortName = ''; 12 | if (params.firstName?.isNotEmpty == true) { 13 | shortName = params.firstName?[0] ?? ''; 14 | } 15 | if (params.lastName?.isNotEmpty == true) { 16 | shortName += params.lastName?[0] ?? ''; 17 | } 18 | 19 | if (shortName.isEmpty) { 20 | shortName = params.email[0]; 21 | } 22 | 23 | return UserDetailsOutput( 24 | shortName: shortName.toUpperCase(), 25 | displayName: displayName, 26 | ); 27 | } 28 | } 29 | 30 | final class UserDetailsParams { 31 | const UserDetailsParams({ 32 | required this.firstName, 33 | required this.lastName, 34 | required this.email, 35 | }); 36 | 37 | final String? firstName; 38 | final String? lastName; 39 | final String email; 40 | } 41 | 42 | final class UserDetailsOutput { 43 | const UserDetailsOutput({ 44 | required this.shortName, 45 | required this.displayName, 46 | }); 47 | 48 | final String shortName; 49 | final String displayName; 50 | } 51 | -------------------------------------------------------------------------------- /lib/firebase_options.dart: -------------------------------------------------------------------------------- 1 | // File generated by FlutterFire CLI. 2 | // ignore_for_file: lines_longer_than_80_chars, avoid_classes_with_only_static_members 3 | import 'package:firebase_core/firebase_core.dart' show FirebaseOptions; 4 | 5 | /// Default [FirebaseOptions] for use with your Firebase apps. 6 | /// 7 | /// Example: 8 | /// ```dart 9 | /// import 'firebase_options.dart'; 10 | /// // ... 11 | /// await Firebase.initializeApp( 12 | /// options: DefaultFirebaseOptions.currentPlatform, 13 | /// ); 14 | /// ``` 15 | class DefaultFirebaseOptions { 16 | static FirebaseOptions get currentPlatform { 17 | throw UnsupportedError( 18 | 'Firebase have not been configured - ' 19 | 'you can reconfigure this by running the FlutterFire CLI again.', 20 | ); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /lib/modules/alarm/alarm_routes.dart: -------------------------------------------------------------------------------- 1 | import 'package:fluro/fluro.dart'; 2 | import 'package:thingsboard_app/config/routes/router.dart'; 3 | import 'package:thingsboard_app/core/context/tb_context.dart'; 4 | import 'package:thingsboard_app/modules/alarm/presentation/view/alarm_details_page.dart'; 5 | import 'package:thingsboard_app/modules/alarm/presentation/view/alarms_page.dart'; 6 | import 'package:thingsboard_app/modules/alarm/presentation/view/alarms_search_page.dart'; 7 | 8 | class AlarmRoutes extends TbRoutes { 9 | AlarmRoutes(TbContext tbContext) : super(tbContext); 10 | 11 | late final alarmsHandler = Handler( 12 | handlerFunc: (context, params) { 13 | final searchMode = params['search']?.first == 'true'; 14 | if (!searchMode) { 15 | return AlarmsPage( 16 | tbContext, 17 | searchMode: params['search']?.first == 'true', 18 | ); 19 | } else { 20 | return AlarmsSearchPage( 21 | tbContext: tbContext, 22 | ); 23 | } 24 | }, 25 | ); 26 | 27 | late final alarmDetailsHandler = Handler( 28 | handlerFunc: (_, params) { 29 | return AlarmDetailsPage(tbContext, id: params['id']![0]); 30 | }, 31 | ); 32 | 33 | @override 34 | void doRegisterRoutes(router) { 35 | router.define('/alarms', handler: alarmsHandler); 36 | router.define('/alarmDetails/:id', handler: alarmDetailsHandler); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /lib/modules/alarm/alarms_base.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:thingsboard_app/thingsboard_client.dart'; 3 | 4 | final alarmSeverityColors = { 5 | AlarmSeverity.CRITICAL: const Color(0xFFD12730), 6 | AlarmSeverity.MAJOR: const Color(0xFFF66716), 7 | AlarmSeverity.MINOR: const Color(0xFFF2DA05), 8 | AlarmSeverity.WARNING: const Color(0xFFFAA405), 9 | AlarmSeverity.INDETERMINATE: Colors.black.withOpacity(0.38), 10 | }; 11 | 12 | const alarmSeverityTranslations = { 13 | AlarmSeverity.CRITICAL: 'Critical', 14 | AlarmSeverity.MAJOR: 'Major', 15 | AlarmSeverity.MINOR: 'Minor', 16 | AlarmSeverity.WARNING: 'Warning', 17 | AlarmSeverity.INDETERMINATE: 'Indeterminate', 18 | }; 19 | 20 | const alarmStatusTranslations = { 21 | AlarmStatus.ACTIVE_ACK: 'Active Acknowledged', 22 | AlarmStatus.ACTIVE_UNACK: 'Active Unacknowledged', 23 | AlarmStatus.CLEARED_ACK: 'Cleared Acknowledged', 24 | AlarmStatus.CLEARED_UNACK: 'Cleared Unacknowledged', 25 | }; 26 | -------------------------------------------------------------------------------- /lib/modules/alarm/data/datasource/alarm_types/alarm_types_datasource.dart: -------------------------------------------------------------------------------- 1 | import 'package:thingsboard_app/modules/alarm/data/datasource/alarm_types/i_alarm_types_datasource.dart'; 2 | import 'package:thingsboard_app/thingsboard_client.dart'; 3 | 4 | class AlarmTypesDatasource implements IAlarmTypesDatasource { 5 | const AlarmTypesDatasource({required this.tbClient}); 6 | 7 | final ThingsboardClient tbClient; 8 | 9 | @override 10 | Future> fetchAlarmTypes(PageLink pageKey) { 11 | return tbClient.getAlarmService().getAlarmTypes(pageKey); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /lib/modules/alarm/data/datasource/alarm_types/i_alarm_types_datasource.dart: -------------------------------------------------------------------------------- 1 | import 'package:thingsboard_app/thingsboard_client.dart'; 2 | 3 | abstract interface class IAlarmTypesDatasource { 4 | Future> fetchAlarmTypes(PageLink pageKey); 5 | } 6 | -------------------------------------------------------------------------------- /lib/modules/alarm/data/datasource/alarms/alarms_datasource.dart: -------------------------------------------------------------------------------- 1 | import 'package:thingsboard_app/modules/alarm/data/datasource/alarms/i_alarms_datasource.dart'; 2 | import 'package:thingsboard_app/thingsboard_client.dart'; 3 | 4 | class AlarmsDatasource implements IAlarmsDatasource { 5 | const AlarmsDatasource({required this.thingsboardClient}); 6 | 7 | final ThingsboardClient thingsboardClient; 8 | 9 | @override 10 | Future> fetchAlarms(AlarmQueryV2 query) { 11 | return thingsboardClient.getAlarmService().getAllAlarmsV2(query); 12 | } 13 | 14 | @override 15 | Future getAlarmInfo(String id) { 16 | return thingsboardClient.getAlarmService().getAlarmInfo(id); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /lib/modules/alarm/data/datasource/alarms/i_alarms_datasource.dart: -------------------------------------------------------------------------------- 1 | import 'package:thingsboard_app/thingsboard_client.dart'; 2 | 3 | abstract interface class IAlarmsDatasource { 4 | Future> fetchAlarms(AlarmQueryV2 query); 5 | 6 | Future getAlarmInfo(String id); 7 | } 8 | -------------------------------------------------------------------------------- /lib/modules/alarm/data/datasource/assignee/assignee_datasource.dart: -------------------------------------------------------------------------------- 1 | import 'package:thingsboard_app/modules/alarm/data/datasource/assignee/i_assignee_datasource.dart'; 2 | import 'package:thingsboard_app/thingsboard_client.dart'; 3 | 4 | class AssigneeDatasource implements IAssigneeDatasource { 5 | const AssigneeDatasource({required this.tbClient}); 6 | 7 | final ThingsboardClient tbClient; 8 | 9 | @override 10 | Future> fetchAssignee(PageLink pageKey) async { 11 | return tbClient.getUserService().getUsersInfo(pageKey); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /lib/modules/alarm/data/datasource/assignee/i_assignee_datasource.dart: -------------------------------------------------------------------------------- 1 | import 'package:thingsboard_app/thingsboard_client.dart'; 2 | 3 | abstract interface class IAssigneeDatasource { 4 | Future> fetchAssignee(PageLink pageKey); 5 | } 6 | -------------------------------------------------------------------------------- /lib/modules/alarm/data/datasource/details/i_alarm_details_datasource.dart: -------------------------------------------------------------------------------- 1 | import 'package:thingsboard_app/thingsboard_client.dart'; 2 | 3 | abstract interface class IAlarmDetailsDatasource { 4 | Future> fetchAlarmComments( 5 | AlarmCommentsQuery query, 6 | ); 7 | 8 | Future acknowledgeAlarm(AlarmId id); 9 | 10 | Future clearAlarm(AlarmId id); 11 | 12 | Future postComment( 13 | AlarmId alarmId, { 14 | required String comment, 15 | }); 16 | 17 | Future updateComment( 18 | AlarmId alarmId, { 19 | required String id, 20 | required String comment, 21 | }); 22 | 23 | Future deleteComment(AlarmId id, {required String commentId}); 24 | 25 | Future> fetchAssignee(UsersAssignQuery query); 26 | 27 | Future assignAlarm(String alarmId, String assigneeId); 28 | 29 | Future unassignAlarm(String alarmId); 30 | } 31 | -------------------------------------------------------------------------------- /lib/modules/alarm/data/repository/alarm_types/alarm_types_repository.dart: -------------------------------------------------------------------------------- 1 | import 'package:thingsboard_app/modules/alarm/data/datasource/alarm_types/i_alarm_types_datasource.dart'; 2 | import 'package:thingsboard_app/modules/alarm/domain/repository/alarm_types/i_alarm_types_repository.dart'; 3 | import 'package:thingsboard_app/thingsboard_client.dart'; 4 | 5 | class AlarmTypesRepository implements IAlarmTypesRepository { 6 | const AlarmTypesRepository({required this.datasource}); 7 | 8 | final IAlarmTypesDatasource datasource; 9 | 10 | @override 11 | Future> fetchAlarmTypes(PageLink pageKey) async { 12 | return datasource.fetchAlarmTypes(pageKey); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /lib/modules/alarm/data/repository/alarms/alarms_repository.dart: -------------------------------------------------------------------------------- 1 | import 'package:thingsboard_app/modules/alarm/data/datasource/alarms/i_alarms_datasource.dart'; 2 | import 'package:thingsboard_app/modules/alarm/domain/repository/alarms/i_alarms_repository.dart'; 3 | import 'package:thingsboard_app/thingsboard_client.dart'; 4 | 5 | class AlarmsRepository implements IAlarmsRepository { 6 | const AlarmsRepository({required this.datasource}); 7 | 8 | final IAlarmsDatasource datasource; 9 | 10 | @override 11 | Future> fetchAlarms(AlarmQueryV2 query) { 12 | return datasource.fetchAlarms(query); 13 | } 14 | 15 | @override 16 | Future getAlarmInfo(String id) { 17 | return datasource.getAlarmInfo(id); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /lib/modules/alarm/data/repository/assignee/assignee_repository.dart: -------------------------------------------------------------------------------- 1 | import 'package:thingsboard_app/modules/alarm/data/datasource/assignee/i_assignee_datasource.dart'; 2 | import 'package:thingsboard_app/modules/alarm/domain/repository/assignee/i_assigne_repository.dart'; 3 | import 'package:thingsboard_app/thingsboard_client.dart'; 4 | 5 | class AssigneeRepository implements IAssigneeRepository { 6 | const AssigneeRepository({required this.datasource}); 7 | 8 | final IAssigneeDatasource datasource; 9 | 10 | @override 11 | Future> fetchAssignee(PageLink pageKey) async { 12 | return datasource.fetchAssignee(pageKey); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /lib/modules/alarm/domain/entities/alarm_comment_entity.dart: -------------------------------------------------------------------------------- 1 | import 'package:thingsboard_app/thingsboard_client.dart'; 2 | 3 | class AlarmCommentEntity { 4 | const AlarmCommentEntity(this.commentInfo, {required this.canEdit}); 5 | 6 | final AlarmCommentInfo commentInfo; 7 | final bool canEdit; 8 | } 9 | -------------------------------------------------------------------------------- /lib/modules/alarm/domain/entities/alarm_filters_entity.dart: -------------------------------------------------------------------------------- 1 | import 'package:equatable/equatable.dart'; 2 | import 'package:thingsboard_app/thingsboard_client.dart'; 3 | 4 | class AlarmFiltersEntity extends Equatable { 5 | const AlarmFiltersEntity({ 6 | this.typeList, 7 | this.statusList = const [AlarmSearchStatus.ACTIVE], 8 | this.severityList, 9 | this.assigneeId, 10 | this.selfAssignee, 11 | }); 12 | 13 | final List? typeList; 14 | final List? statusList; 15 | final List? severityList; 16 | final UserId? assigneeId; 17 | final bool? selfAssignee; 18 | 19 | factory AlarmFiltersEntity.defaultFilters() { 20 | return const AlarmFiltersEntity(); 21 | } 22 | 23 | factory AlarmFiltersEntity.fromUiFilters({ 24 | required List typeList, 25 | required List status, 26 | required List severity, 27 | required String? userId, 28 | }) { 29 | return AlarmFiltersEntity( 30 | typeList: typeList.isNotEmpty ? typeList : null, 31 | statusList: status.isNotEmpty ? status : null, 32 | severityList: severity.isNotEmpty ? severity : null, 33 | assigneeId: userId != null ? UserId(userId) : null, 34 | ); 35 | } 36 | 37 | @override 38 | String toString() { 39 | return 'AlarmFiltersEntity(typeList: $typeList, statusList: $statusList, ' 40 | 'severityList: $severityList, assigneeId: $assigneeId)'; 41 | } 42 | 43 | @override 44 | List get props => [typeList, statusList, severityList, assigneeId]; 45 | } 46 | -------------------------------------------------------------------------------- /lib/modules/alarm/domain/entities/assignee_entity.dart: -------------------------------------------------------------------------------- 1 | import 'package:equatable/equatable.dart'; 2 | import 'package:thingsboard_app/core/usecases/user_details_usecase.dart'; 3 | import 'package:thingsboard_app/thingsboard_client.dart'; 4 | 5 | class AssigneeEntity extends Equatable { 6 | const AssigneeEntity({ 7 | required this.userInfo, 8 | required this.shortName, 9 | required this.displayName, 10 | }); 11 | 12 | final UserInfo userInfo; 13 | final String shortName; 14 | final String displayName; 15 | 16 | factory AssigneeEntity.fromUserInfo( 17 | UserInfo info, { 18 | required UserDetailsUseCase detailsUseCase, 19 | }) { 20 | final details = detailsUseCase( 21 | UserDetailsParams( 22 | firstName: info.firstName, 23 | lastName: info.lastName, 24 | email: info.email, 25 | ), 26 | ); 27 | 28 | return AssigneeEntity( 29 | userInfo: info, 30 | displayName: details.displayName, 31 | shortName: details.shortName, 32 | ); 33 | } 34 | 35 | @override 36 | List get props => [userInfo, shortName, displayName]; 37 | } 38 | -------------------------------------------------------------------------------- /lib/modules/alarm/domain/entities/filter_data_entity.dart: -------------------------------------------------------------------------------- 1 | import 'package:equatable/equatable.dart'; 2 | 3 | class FilterDataEntity extends Equatable { 4 | const FilterDataEntity({required this.label, required this.data}); 5 | 6 | final String label; 7 | final T data; 8 | 9 | @override 10 | List get props => [label, data]; 11 | } 12 | -------------------------------------------------------------------------------- /lib/modules/alarm/domain/pagination/activity/alarm_activity_pagination_repository.dart: -------------------------------------------------------------------------------- 1 | import 'package:thingsboard_app/modules/alarm/domain/pagination/activity/alarm_activity_query_ctrl.dart'; 2 | import 'package:thingsboard_app/thingsboard_client.dart'; 3 | import 'package:thingsboard_app/utils/services/pagination_repository.dart'; 4 | 5 | final class AlarmActivityPaginationRepository 6 | extends PaginationRepository { 7 | AlarmActivityPaginationRepository({ 8 | required AlarmActivityQueryCtrl queryCtrl, 9 | required this.onFetchPageData, 10 | }) : super(pageKeyController: queryCtrl); 11 | 12 | final Future> Function(AlarmCommentsQuery) 13 | onFetchPageData; 14 | 15 | @override 16 | Future> fetchPageData(AlarmCommentsQuery pageKey) { 17 | return onFetchPageData(pageKey); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /lib/modules/alarm/domain/pagination/activity/alarm_activity_query_ctrl.dart: -------------------------------------------------------------------------------- 1 | import 'package:thingsboard_app/core/entity/entities_base.dart'; 2 | import 'package:thingsboard_app/thingsboard_client.dart'; 3 | 4 | class AlarmActivityQueryCtrl extends PageKeyController { 5 | AlarmActivityQueryCtrl({ 6 | required AlarmId id, 7 | int pageSize = 20, 8 | SortOrder? sortOrder, 9 | }) : super( 10 | AlarmCommentsQuery( 11 | pageLink: PageLink( 12 | pageSize, 13 | 0, 14 | null, 15 | sortOrder ?? SortOrder('createdTime', Direction.ASC), 16 | ), 17 | id: id, 18 | ), 19 | ); 20 | 21 | @override 22 | AlarmCommentsQuery nextPageKey(AlarmCommentsQuery pageKey) { 23 | return AlarmCommentsQuery( 24 | pageLink: pageKey.pageLink.nextPageLink(), 25 | id: pageKey.id, 26 | ); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /lib/modules/alarm/domain/pagination/alarm_types/alarm_types_pagination_repository.dart: -------------------------------------------------------------------------------- 1 | import 'package:thingsboard_app/modules/alarm/domain/pagination/alarm_types/alarm_types_query_ctrl.dart'; 2 | import 'package:thingsboard_app/thingsboard_client.dart'; 3 | import 'package:thingsboard_app/utils/services/pagination_repository.dart'; 4 | 5 | final class AlarmTypesPaginationRepository 6 | extends PaginationRepository { 7 | AlarmTypesPaginationRepository({ 8 | required AlarmTypesQueryCtrl alarmTypesQueryCtrl, 9 | required this.onFetchPageData, 10 | }) : super(pageKeyController: alarmTypesQueryCtrl); 11 | 12 | final Future> Function(PageLink) onFetchPageData; 13 | 14 | @override 15 | Future> fetchPageData(PageLink pageKey) async { 16 | return onFetchPageData(pageKey); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /lib/modules/alarm/domain/pagination/alarm_types/alarm_types_query_ctrl.dart: -------------------------------------------------------------------------------- 1 | import 'package:thingsboard_app/core/entity/entities_base.dart'; 2 | import 'package:thingsboard_app/thingsboard_client.dart'; 3 | 4 | class AlarmTypesQueryCtrl extends PageKeyController { 5 | AlarmTypesQueryCtrl({ 6 | int pageSize = 20, 7 | String? searchText, 8 | SortOrder? sortOrder, 9 | }) : super( 10 | PageLink( 11 | pageSize, 12 | 0, 13 | searchText, 14 | sortOrder, 15 | ), 16 | ); 17 | 18 | @override 19 | PageLink nextPageKey(PageLink pageKey) { 20 | return pageKey.nextPageLink(); 21 | } 22 | 23 | void refresh() { 24 | notifyListeners(); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /lib/modules/alarm/domain/pagination/alarms/alarms_pagination_repository.dart: -------------------------------------------------------------------------------- 1 | import 'package:thingsboard_app/modules/alarm/domain/pagination/alarms/alarms_query_ctrl.dart'; 2 | import 'package:thingsboard_app/thingsboard_client.dart'; 3 | import 'package:thingsboard_app/utils/services/pagination_repository.dart'; 4 | 5 | final class AlarmsPaginationRepository 6 | extends PaginationRepository { 7 | AlarmsPaginationRepository({ 8 | required AlarmQueryController queryController, 9 | required this.onFetchData, 10 | }) : super(pageKeyController: queryController); 11 | 12 | final Future> Function(AlarmQueryV2 query) onFetchData; 13 | 14 | @override 15 | Future> fetchPageData(AlarmQueryV2 pageKey) { 16 | return onFetchData(pageKey); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /lib/modules/alarm/domain/pagination/assignee/alarm_assignee_pagiation_repository.dart: -------------------------------------------------------------------------------- 1 | import 'package:thingsboard_app/modules/alarm/domain/entities/assignee_entity.dart'; 2 | import 'package:thingsboard_app/modules/alarm/domain/pagination/assignee/alarm_assignee_query_ctrl.dart'; 3 | import 'package:thingsboard_app/thingsboard_client.dart'; 4 | import 'package:thingsboard_app/utils/services/pagination_repository.dart'; 5 | 6 | final class AlarmAssigneePaginationRepository 7 | extends PaginationRepository { 8 | AlarmAssigneePaginationRepository({ 9 | required AlarmAssigneeQueryCtrl assigneeQueryCtrl, 10 | required this.onFetchPageData, 11 | }) : super(pageKeyController: assigneeQueryCtrl); 12 | 13 | final Future> Function(UsersAssignQuery) 14 | onFetchPageData; 15 | 16 | @override 17 | Future> fetchPageData( 18 | UsersAssignQuery pageKey, 19 | ) async { 20 | return onFetchPageData(pageKey); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /lib/modules/alarm/domain/pagination/assignee/alarm_assignee_query_ctrl.dart: -------------------------------------------------------------------------------- 1 | import 'package:thingsboard_app/core/entity/entities_base.dart'; 2 | import 'package:thingsboard_app/thingsboard_client.dart'; 3 | 4 | class AlarmAssigneeQueryCtrl extends PageKeyController { 5 | AlarmAssigneeQueryCtrl({ 6 | required AlarmId id, 7 | int pageSize = 50, 8 | String? searchText, 9 | SortOrder? sortOrder, 10 | }) : super( 11 | UsersAssignQuery( 12 | pageLink: PageLink( 13 | pageSize, 14 | 0, 15 | searchText, 16 | sortOrder ?? SortOrder('email', Direction.ASC), 17 | ), 18 | id: id, 19 | ), 20 | ); 21 | 22 | @override 23 | UsersAssignQuery nextPageKey(UsersAssignQuery pageKey) { 24 | return UsersAssignQuery( 25 | pageLink: pageKey.pageLink.nextPageLink(), 26 | id: pageKey.id, 27 | ); 28 | } 29 | 30 | void onSearchText(String? searchText) { 31 | final query = value.pageKey; 32 | query.pageLink.page = 0; 33 | query.pageLink.textSearch = searchText; 34 | 35 | notifyListeners(); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /lib/modules/alarm/domain/pagination/assignee/assignee_pagination_repository.dart: -------------------------------------------------------------------------------- 1 | import 'package:thingsboard_app/modules/alarm/domain/entities/assignee_entity.dart'; 2 | import 'package:thingsboard_app/modules/alarm/domain/pagination/assignee/assignee_query_ctrl.dart'; 3 | import 'package:thingsboard_app/thingsboard_client.dart'; 4 | import 'package:thingsboard_app/utils/services/pagination_repository.dart'; 5 | 6 | final class AssigneePaginationRepository 7 | extends PaginationRepository { 8 | AssigneePaginationRepository({ 9 | required AssigneeQueryCtrl assigneeQueryCtrl, 10 | required this.onFetchPageData, 11 | }) : super(pageKeyController: assigneeQueryCtrl); 12 | 13 | final Future> Function(PageLink) onFetchPageData; 14 | 15 | @override 16 | Future> fetchPageData(PageLink pageKey) async { 17 | return onFetchPageData(pageKey); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /lib/modules/alarm/domain/pagination/assignee/assignee_query_ctrl.dart: -------------------------------------------------------------------------------- 1 | import 'package:thingsboard_app/core/entity/entities_base.dart'; 2 | import 'package:thingsboard_app/thingsboard_client.dart'; 3 | 4 | class AssigneeQueryCtrl extends PageKeyController { 5 | AssigneeQueryCtrl({ 6 | int pageSize = 50, 7 | String? searchText, 8 | SortOrder? sortOrder, 9 | }) : super( 10 | PageLink( 11 | pageSize, 12 | 0, 13 | searchText, 14 | sortOrder ?? 15 | SortOrder( 16 | 'email', 17 | Direction.ASC, 18 | ), 19 | ), 20 | ); 21 | 22 | @override 23 | PageLink nextPageKey(PageLink pageKey) { 24 | return pageKey.nextPageLink(); 25 | } 26 | 27 | void onSearchText(String? searchText) { 28 | final query = value.pageKey; 29 | query.page = 0; 30 | query.textSearch = searchText; 31 | 32 | notifyListeners(); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /lib/modules/alarm/domain/repository/alarm_types/i_alarm_types_repository.dart: -------------------------------------------------------------------------------- 1 | import 'package:thingsboard_app/thingsboard_client.dart'; 2 | 3 | abstract interface class IAlarmTypesRepository { 4 | Future> fetchAlarmTypes(PageLink pageKey); 5 | } 6 | -------------------------------------------------------------------------------- /lib/modules/alarm/domain/repository/alarms/i_alarms_repository.dart: -------------------------------------------------------------------------------- 1 | import 'package:thingsboard_app/thingsboard_client.dart'; 2 | 3 | abstract interface class IAlarmsRepository { 4 | Future> fetchAlarms(AlarmQueryV2 query); 5 | 6 | Future getAlarmInfo(String id); 7 | } 8 | -------------------------------------------------------------------------------- /lib/modules/alarm/domain/repository/assignee/i_assigne_repository.dart: -------------------------------------------------------------------------------- 1 | import 'package:thingsboard_app/thingsboard_client.dart'; 2 | 3 | abstract interface class IAssigneeRepository { 4 | Future> fetchAssignee(PageLink pageKey); 5 | } 6 | -------------------------------------------------------------------------------- /lib/modules/alarm/domain/repository/details/i_alarm_details_repository.dart: -------------------------------------------------------------------------------- 1 | import 'package:thingsboard_app/thingsboard_client.dart'; 2 | 3 | abstract interface class IAlarmDetailsRepository { 4 | Future> fetchAlarmComments( 5 | AlarmCommentsQuery query, 6 | ); 7 | 8 | Future acknowledgeAlarm(AlarmId id); 9 | 10 | Future clearAlarm(AlarmId id); 11 | 12 | Future postComment( 13 | AlarmId alarmId, { 14 | required String comment, 15 | }); 16 | 17 | Future updateComment( 18 | AlarmId alarmId, { 19 | required String id, 20 | required String comment, 21 | }); 22 | 23 | Future deleteComment(AlarmId id, {required String commentId}); 24 | 25 | Future> fetchAssignee(UsersAssignQuery query); 26 | 27 | Future assignAlarm(String alarmId, String assigneeId); 28 | 29 | Future unassignAlarm(String alarmId); 30 | } 31 | -------------------------------------------------------------------------------- /lib/modules/alarm/domain/usecases/alarm_types/fetch_alarm_types_usecase.dart: -------------------------------------------------------------------------------- 1 | import 'package:thingsboard_app/modules/alarm/domain/repository/alarm_types/i_alarm_types_repository.dart'; 2 | import 'package:thingsboard_app/thingsboard_client.dart'; 3 | import 'package:thingsboard_app/utils/usecase.dart'; 4 | 5 | class FetchAlarmTypesUseCase 6 | extends UseCase>, PageLink> { 7 | const FetchAlarmTypesUseCase({required this.repository}); 8 | 9 | final IAlarmTypesRepository repository; 10 | 11 | @override 12 | Future> call(PageLink params) async { 13 | return repository.fetchAlarmTypes(params); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /lib/modules/alarm/domain/usecases/alarms/fetch_alarm_usecase.dart: -------------------------------------------------------------------------------- 1 | import 'package:thingsboard_app/modules/alarm/domain/repository/alarms/i_alarms_repository.dart'; 2 | import 'package:thingsboard_app/thingsboard_client.dart'; 3 | import 'package:thingsboard_app/utils/usecase.dart'; 4 | 5 | class FetchAlarmUseCase extends UseCase, String> { 6 | const FetchAlarmUseCase({required this.repository}); 7 | 8 | final IAlarmsRepository repository; 9 | 10 | @override 11 | Future call(String params) { 12 | return repository.getAlarmInfo(params); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /lib/modules/alarm/domain/usecases/alarms/fetch_alarms_usecase.dart: -------------------------------------------------------------------------------- 1 | import 'package:thingsboard_app/modules/alarm/domain/repository/alarms/i_alarms_repository.dart'; 2 | import 'package:thingsboard_app/thingsboard_client.dart'; 3 | import 'package:thingsboard_app/utils/usecase.dart'; 4 | 5 | class FetchAlarmsUseCase 6 | extends UseCase>, AlarmQueryV2> { 7 | const FetchAlarmsUseCase({required this.repository}); 8 | 9 | final IAlarmsRepository repository; 10 | 11 | @override 12 | Future> call(AlarmQueryV2 params) { 13 | return repository.fetchAlarms(params); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /lib/modules/alarm/domain/usecases/assignee/assign_alarm_usecase.dart: -------------------------------------------------------------------------------- 1 | import 'package:thingsboard_app/modules/alarm/domain/repository/details/i_alarm_details_repository.dart'; 2 | import 'package:thingsboard_app/thingsboard_client.dart'; 3 | import 'package:thingsboard_app/utils/usecase.dart'; 4 | 5 | class AssignAlarmUseCase extends UseCase, AssignAlarmParams> { 6 | const AssignAlarmUseCase(this.repository); 7 | 8 | final IAlarmDetailsRepository repository; 9 | 10 | @override 11 | Future call(AssignAlarmParams params) { 12 | return repository.assignAlarm(params.id, params.userId); 13 | } 14 | } 15 | 16 | final class AssignAlarmParams { 17 | const AssignAlarmParams({ 18 | required this.id, 19 | required this.userId, 20 | }); 21 | 22 | final String id; 23 | final String userId; 24 | } 25 | -------------------------------------------------------------------------------- /lib/modules/alarm/domain/usecases/assignee/fetch_alarm_assignee_usecase.dart: -------------------------------------------------------------------------------- 1 | import 'package:thingsboard_app/core/usecases/user_details_usecase.dart'; 2 | import 'package:thingsboard_app/locator.dart'; 3 | import 'package:thingsboard_app/modules/alarm/domain/entities/assignee_entity.dart'; 4 | import 'package:thingsboard_app/modules/alarm/domain/repository/details/i_alarm_details_repository.dart'; 5 | import 'package:thingsboard_app/thingsboard_client.dart'; 6 | import 'package:thingsboard_app/utils/usecase.dart'; 7 | 8 | class FetchAlarmAssigneeUseCase 9 | extends UseCase>, UsersAssignQuery> { 10 | const FetchAlarmAssigneeUseCase(this.repository); 11 | 12 | final IAlarmDetailsRepository repository; 13 | 14 | @override 15 | Future> call(UsersAssignQuery params) async { 16 | final pageData = await repository.fetchAssignee(params); 17 | 18 | return PageData( 19 | pageData.data 20 | .map( 21 | (info) => AssigneeEntity.fromUserInfo( 22 | info, 23 | detailsUseCase: getIt(), 24 | ), 25 | ) 26 | .toList(), 27 | pageData.totalPages, 28 | pageData.totalElements, 29 | pageData.hasNext, 30 | ); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /lib/modules/alarm/domain/usecases/assignee/fetch_assignee_usecase.dart: -------------------------------------------------------------------------------- 1 | import 'package:thingsboard_app/core/usecases/user_details_usecase.dart'; 2 | import 'package:thingsboard_app/locator.dart'; 3 | import 'package:thingsboard_app/modules/alarm/domain/entities/assignee_entity.dart'; 4 | import 'package:thingsboard_app/modules/alarm/domain/repository/assignee/i_assigne_repository.dart'; 5 | import 'package:thingsboard_app/thingsboard_client.dart'; 6 | import 'package:thingsboard_app/utils/usecase.dart'; 7 | 8 | class FetchAssigneeUseCase 9 | extends UseCase>, PageLink> { 10 | const FetchAssigneeUseCase({required this.repository}); 11 | 12 | final IAssigneeRepository repository; 13 | 14 | @override 15 | Future> call(PageLink params) async { 16 | final pageData = await repository.fetchAssignee(params); 17 | 18 | return PageData( 19 | pageData.data 20 | .map( 21 | (info) => AssigneeEntity.fromUserInfo( 22 | info, 23 | detailsUseCase: getIt(), 24 | ), 25 | ) 26 | .toList(), 27 | pageData.totalPages, 28 | pageData.totalElements, 29 | pageData.hasNext, 30 | ); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /lib/modules/alarm/domain/usecases/assignee/unassign_alarm_usecase.dart: -------------------------------------------------------------------------------- 1 | import 'package:thingsboard_app/modules/alarm/domain/repository/details/i_alarm_details_repository.dart'; 2 | import 'package:thingsboard_app/thingsboard_client.dart'; 3 | import 'package:thingsboard_app/utils/usecase.dart'; 4 | 5 | final class UnassignAlarmUseCase extends UseCase, String> { 6 | const UnassignAlarmUseCase(this.repository); 7 | 8 | final IAlarmDetailsRepository repository; 9 | 10 | @override 11 | Future call(String params) { 12 | return repository.unassignAlarm(params); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /lib/modules/alarm/domain/usecases/details/acknowledge_alarm_usecase.dart: -------------------------------------------------------------------------------- 1 | import 'package:thingsboard_app/modules/alarm/domain/repository/details/i_alarm_details_repository.dart'; 2 | import 'package:thingsboard_app/thingsboard_client.dart'; 3 | import 'package:thingsboard_app/utils/usecase.dart'; 4 | 5 | final class AcknowledgeAlarmUseCase 6 | extends UseCase, AlarmId> { 7 | const AcknowledgeAlarmUseCase(this.repository); 8 | 9 | final IAlarmDetailsRepository repository; 10 | 11 | @override 12 | Future call(AlarmId params) { 13 | return repository.acknowledgeAlarm(params); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /lib/modules/alarm/domain/usecases/details/clear_alarm_usecase.dart: -------------------------------------------------------------------------------- 1 | import 'package:thingsboard_app/modules/alarm/domain/repository/details/i_alarm_details_repository.dart'; 2 | import 'package:thingsboard_app/thingsboard_client.dart'; 3 | import 'package:thingsboard_app/utils/usecase.dart'; 4 | 5 | final class ClearAlarmUseCase extends UseCase, AlarmId> { 6 | const ClearAlarmUseCase(this.repository); 7 | 8 | final IAlarmDetailsRepository repository; 9 | 10 | @override 11 | Future call(AlarmId params) { 12 | return repository.clearAlarm(params); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /lib/modules/alarm/domain/usecases/details/delete_alarm_comment_usecase.dart: -------------------------------------------------------------------------------- 1 | import 'package:thingsboard_app/modules/alarm/domain/repository/details/i_alarm_details_repository.dart'; 2 | import 'package:thingsboard_app/thingsboard_client.dart'; 3 | import 'package:thingsboard_app/utils/usecase.dart'; 4 | 5 | final class DeleteAlarmCommentUseCase 6 | extends UseCase, DeleteCommentParams> { 7 | DeleteAlarmCommentUseCase(this.repository); 8 | 9 | final IAlarmDetailsRepository repository; 10 | 11 | @override 12 | Future call(DeleteCommentParams params) async { 13 | return repository.deleteComment( 14 | params.alarmId, 15 | commentId: params.commentId, 16 | ); 17 | } 18 | } 19 | 20 | final class DeleteCommentParams { 21 | const DeleteCommentParams({required this.alarmId, required this.commentId}); 22 | 23 | final String commentId; 24 | final AlarmId alarmId; 25 | } 26 | -------------------------------------------------------------------------------- /lib/modules/alarm/domain/usecases/details/fetch_alarm_comments_usecase.dart: -------------------------------------------------------------------------------- 1 | import 'package:thingsboard_app/modules/alarm/domain/repository/details/i_alarm_details_repository.dart'; 2 | import 'package:thingsboard_app/thingsboard_client.dart'; 3 | import 'package:thingsboard_app/utils/usecase.dart'; 4 | 5 | class FetchAlarmCommentsUseCase 6 | extends UseCase>, AlarmCommentsQuery> { 7 | const FetchAlarmCommentsUseCase(this.repository); 8 | 9 | final IAlarmDetailsRepository repository; 10 | 11 | @override 12 | Future> call(AlarmCommentsQuery params) { 13 | return repository.fetchAlarmComments(params); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /lib/modules/alarm/domain/usecases/details/post_alarm_comments_usecase.dart: -------------------------------------------------------------------------------- 1 | import 'package:thingsboard_app/modules/alarm/domain/repository/details/i_alarm_details_repository.dart'; 2 | import 'package:thingsboard_app/thingsboard_client.dart'; 3 | import 'package:thingsboard_app/utils/usecase.dart'; 4 | 5 | final class PostAlarmCommentsUseCase 6 | extends UseCase, PostAlarmCommentParams> { 7 | const PostAlarmCommentsUseCase(this.repository); 8 | 9 | final IAlarmDetailsRepository repository; 10 | 11 | @override 12 | Future call(PostAlarmCommentParams params) { 13 | final comment = params.comment.trimLeft().trimRight(); 14 | if (comment.isEmpty) { 15 | throw Exception('An empty comment was rejected to be posted.'); 16 | } 17 | 18 | if (params.id != null) { 19 | return repository.updateComment( 20 | params.alarmId, 21 | id: params.id!, 22 | comment: comment, 23 | ); 24 | } 25 | 26 | return repository.postComment(params.alarmId, comment: comment); 27 | } 28 | } 29 | 30 | final class PostAlarmCommentParams { 31 | const PostAlarmCommentParams({ 32 | required this.alarmId, 33 | required this.comment, 34 | this.id, 35 | }); 36 | 37 | final AlarmId alarmId; 38 | final String? id; 39 | final String comment; 40 | } 41 | -------------------------------------------------------------------------------- /lib/modules/alarm/presentation/bloc/activity/alarm_activity_states.dart: -------------------------------------------------------------------------------- 1 | import 'package:equatable/equatable.dart'; 2 | import 'package:thingsboard_app/thingsboard_client.dart'; 3 | 4 | sealed class AlarmActivityState extends Equatable { 5 | const AlarmActivityState(); 6 | 7 | @override 8 | List get props => []; 9 | } 10 | 11 | final class AlarmActivityLoadingState extends AlarmActivityState { 12 | const AlarmActivityLoadingState(); 13 | } 14 | 15 | final class AlarmActivityLoadedState extends AlarmActivityState { 16 | const AlarmActivityLoadedState({ 17 | required this.displayName, 18 | required this.shortName, 19 | required this.hasSomeActivity, 20 | }); 21 | 22 | final String displayName; 23 | final String shortName; 24 | final bool hasSomeActivity; 25 | 26 | @override 27 | List get props => [displayName, shortName, hasSomeActivity]; 28 | } 29 | 30 | final class AlarmCommentEditState extends AlarmActivityState { 31 | const AlarmCommentEditState( 32 | this.commentId, { 33 | required this.alarmId, 34 | required this.commentToEdit, 35 | }); 36 | 37 | final AlarmId alarmId; 38 | final String commentId; 39 | final AlarmComment commentToEdit; 40 | 41 | @override 42 | List get props => [commentId, alarmId, commentToEdit]; 43 | } 44 | -------------------------------------------------------------------------------- /lib/modules/alarm/presentation/bloc/activity/bloc.dart: -------------------------------------------------------------------------------- 1 | export 'alarm_activity_bloc.dart'; 2 | export 'alarm_activity_events.dart'; 3 | export 'alarm_activity_states.dart'; 4 | -------------------------------------------------------------------------------- /lib/modules/alarm/presentation/bloc/alarm_assignee/alarm_assignee_event.dart: -------------------------------------------------------------------------------- 1 | import 'package:equatable/equatable.dart'; 2 | 3 | sealed class AlarmAssigneeEvent extends Equatable { 4 | const AlarmAssigneeEvent(); 5 | 6 | @override 7 | List get props => []; 8 | } 9 | 10 | final class AlarmFetchAssigneeEvent extends AlarmAssigneeEvent { 11 | const AlarmFetchAssigneeEvent(); 12 | } 13 | 14 | final class AlarmAssigneeSelectedEvent extends AlarmAssigneeEvent { 15 | const AlarmAssigneeSelectedEvent(this.userId); 16 | 17 | final String userId; 18 | 19 | @override 20 | List get props => [userId]; 21 | } 22 | 23 | final class AlarmAssigneeSearchEvent extends AlarmAssigneeEvent { 24 | const AlarmAssigneeSearchEvent({required this.searchText}); 25 | 26 | final String searchText; 27 | 28 | @override 29 | List get props => [searchText]; 30 | } 31 | 32 | final class AlarmAssigneeResetSearchTextEvent extends AlarmAssigneeEvent { 33 | const AlarmAssigneeResetSearchTextEvent(); 34 | } 35 | 36 | final class AlarmAssigneeUnassignedEvent extends AlarmAssigneeEvent { 37 | const AlarmAssigneeUnassignedEvent(); 38 | } 39 | 40 | final class AlarmAssigneeRefreshEvent extends AlarmAssigneeEvent { 41 | const AlarmAssigneeRefreshEvent(); 42 | } 43 | -------------------------------------------------------------------------------- /lib/modules/alarm/presentation/bloc/alarm_assignee/alarm_assignee_state.dart: -------------------------------------------------------------------------------- 1 | import 'package:equatable/equatable.dart'; 2 | import 'package:thingsboard_app/modules/alarm/domain/entities/assignee_entity.dart'; 3 | 4 | sealed class AlarmAssigneeState extends Equatable { 5 | const AlarmAssigneeState(); 6 | 7 | @override 8 | List get props => []; 9 | } 10 | 11 | final class AlarmAssigneeEmptyState extends AlarmAssigneeState { 12 | const AlarmAssigneeEmptyState(); 13 | } 14 | 15 | final class AlarmAssigneeSelectedState extends AlarmAssigneeState { 16 | const AlarmAssigneeSelectedState(this.assignee); 17 | 18 | final AssigneeEntity assignee; 19 | 20 | @override 21 | List get props => [assignee]; 22 | } 23 | -------------------------------------------------------------------------------- /lib/modules/alarm/presentation/bloc/alarm_assignee/bloc.dart: -------------------------------------------------------------------------------- 1 | export 'alarm_assignee_bloc.dart'; 2 | export 'alarm_assignee_event.dart'; 3 | export 'alarm_assignee_state.dart'; 4 | -------------------------------------------------------------------------------- /lib/modules/alarm/presentation/bloc/alarm_details/alarm_details_events.dart: -------------------------------------------------------------------------------- 1 | import 'package:equatable/equatable.dart'; 2 | import 'package:thingsboard_app/thingsboard_client.dart'; 3 | 4 | sealed class AlarmDetailsEvent extends Equatable { 5 | const AlarmDetailsEvent(); 6 | 7 | @override 8 | List get props => []; 9 | } 10 | 11 | final class AlarmDetailsFetchEvent extends AlarmDetailsEvent { 12 | const AlarmDetailsFetchEvent({required this.id}); 13 | 14 | final String? id; 15 | 16 | @override 17 | List get props => [id]; 18 | } 19 | 20 | final class ClearAlarmEvent extends AlarmDetailsEvent { 21 | const ClearAlarmEvent(this.id); 22 | 23 | final AlarmId id; 24 | 25 | @override 26 | List get props => [id]; 27 | } 28 | 29 | final class AcknowledgeAlarmEvent extends AlarmDetailsEvent { 30 | const AcknowledgeAlarmEvent(this.id); 31 | 32 | final AlarmId id; 33 | 34 | @override 35 | List get props => [id]; 36 | } 37 | -------------------------------------------------------------------------------- /lib/modules/alarm/presentation/bloc/alarm_details/alarm_details_states.dart: -------------------------------------------------------------------------------- 1 | import 'package:equatable/equatable.dart'; 2 | import 'package:thingsboard_app/thingsboard_client.dart'; 3 | 4 | sealed class AlarmDetailsState extends Equatable { 5 | const AlarmDetailsState(); 6 | 7 | @override 8 | List get props => []; 9 | } 10 | 11 | final class AlarmDetailsLoadingState extends AlarmDetailsState { 12 | const AlarmDetailsLoadingState(); 13 | } 14 | 15 | final class AlarmDetailsLoadedState extends AlarmDetailsState { 16 | const AlarmDetailsLoadedState( 17 | this.alarmInfo, { 18 | required this.acknowledge, 19 | required this.clear, 20 | }); 21 | 22 | final AlarmInfo alarmInfo; 23 | final bool acknowledge; 24 | final bool clear; 25 | 26 | @override 27 | List get props => [alarmInfo, acknowledge, clear]; 28 | } 29 | 30 | final class AlarmDetailsErrorState extends AlarmDetailsState { 31 | const AlarmDetailsErrorState(this.message); 32 | 33 | final String message; 34 | 35 | @override 36 | List get props => [message]; 37 | } 38 | -------------------------------------------------------------------------------- /lib/modules/alarm/presentation/bloc/alarm_details/bloc.dart: -------------------------------------------------------------------------------- 1 | export 'alarm_details_bloc.dart'; 2 | export 'alarm_details_events.dart'; 3 | export 'alarm_details_states.dart'; 4 | -------------------------------------------------------------------------------- /lib/modules/alarm/presentation/bloc/alarm_types/alarm_types_event.dart: -------------------------------------------------------------------------------- 1 | import 'package:equatable/equatable.dart'; 2 | 3 | sealed class AlarmTypesEvent extends Equatable { 4 | const AlarmTypesEvent(); 5 | 6 | @override 7 | List get props => []; 8 | } 9 | 10 | final class AlarmTypesSelectedEvent extends AlarmTypesEvent { 11 | const AlarmTypesSelectedEvent({required this.type}); 12 | 13 | final String type; 14 | 15 | @override 16 | List get props => [type]; 17 | } 18 | 19 | final class AlarmTypesRemoveSelectedEvent extends AlarmTypesEvent { 20 | const AlarmTypesRemoveSelectedEvent({required this.type}); 21 | 22 | final String type; 23 | 24 | @override 25 | List get props => [type]; 26 | } 27 | 28 | final class AlarmTypesResetEvent extends AlarmTypesEvent { 29 | const AlarmTypesResetEvent(); 30 | } 31 | 32 | final class AlarmTypesRefreshEvent extends AlarmTypesEvent { 33 | const AlarmTypesRefreshEvent(); 34 | } 35 | 36 | final class AlarmTypesResetUnCommittedChanges extends AlarmTypesEvent { 37 | const AlarmTypesResetUnCommittedChanges(); 38 | } 39 | -------------------------------------------------------------------------------- /lib/modules/alarm/presentation/bloc/alarm_types/alarm_types_state.dart: -------------------------------------------------------------------------------- 1 | import 'package:equatable/equatable.dart'; 2 | 3 | sealed class AlarmTypesState extends Equatable { 4 | const AlarmTypesState(); 5 | 6 | @override 7 | List get props => []; 8 | } 9 | 10 | final class AlarmTypeSelectedState extends AlarmTypesState { 11 | const AlarmTypeSelectedState({ 12 | required this.selectedTypes, 13 | required this.allowToAddMore, 14 | }); 15 | 16 | final Set selectedTypes; 17 | final bool allowToAddMore; 18 | 19 | @override 20 | List get props => [double.nan]; 21 | } 22 | 23 | final class AlarmTypesSelectionEmptyState extends AlarmTypesState { 24 | const AlarmTypesSelectionEmptyState(); 25 | 26 | @override 27 | List get props => []; 28 | } 29 | -------------------------------------------------------------------------------- /lib/modules/alarm/presentation/bloc/alarm_types/bloc.dart: -------------------------------------------------------------------------------- 1 | export 'alarm_types_bloc.dart'; 2 | export 'alarm_types_event.dart'; 3 | export 'alarm_types_state.dart'; 4 | -------------------------------------------------------------------------------- /lib/modules/alarm/presentation/bloc/alarms_events.dart: -------------------------------------------------------------------------------- 1 | import 'package:equatable/equatable.dart'; 2 | import 'package:thingsboard_app/modules/alarm/domain/entities/alarm_filters_entity.dart'; 3 | 4 | sealed class AlarmEvent extends Equatable { 5 | const AlarmEvent(); 6 | 7 | @override 8 | List get props => []; 9 | } 10 | 11 | final class AlarmFiltersResetEvent extends AlarmEvent { 12 | const AlarmFiltersResetEvent(); 13 | } 14 | 15 | final class AlarmFiltersUpdateEvent extends AlarmEvent { 16 | const AlarmFiltersUpdateEvent({required this.filtersEntity}); 17 | 18 | final AlarmFiltersEntity filtersEntity; 19 | 20 | @override 21 | List get props => [filtersEntity]; 22 | } 23 | 24 | final class AlarmSearchTextChanged extends AlarmEvent { 25 | const AlarmSearchTextChanged({required this.searchText}); 26 | 27 | final String? searchText; 28 | 29 | @override 30 | List get props => [searchText]; 31 | } 32 | 33 | final class AlarmsRefreshPageEvent extends AlarmEvent { 34 | const AlarmsRefreshPageEvent(); 35 | 36 | @override 37 | List get props => [double.nan]; 38 | } 39 | -------------------------------------------------------------------------------- /lib/modules/alarm/presentation/bloc/alarms_states.dart: -------------------------------------------------------------------------------- 1 | import 'package:equatable/equatable.dart'; 2 | 3 | sealed class AlarmsState extends Equatable { 4 | const AlarmsState(); 5 | 6 | @override 7 | List get props => []; 8 | } 9 | 10 | final class AlarmsFiltersNotActivatedState extends AlarmsState { 11 | const AlarmsFiltersNotActivatedState(); 12 | } 13 | 14 | final class AlarmsFilterActivatedState extends AlarmsState { 15 | const AlarmsFilterActivatedState(); 16 | } 17 | -------------------------------------------------------------------------------- /lib/modules/alarm/presentation/bloc/assignee/assignee_event.dart: -------------------------------------------------------------------------------- 1 | import 'package:equatable/equatable.dart'; 2 | 3 | sealed class AssigneeEvent extends Equatable { 4 | const AssigneeEvent(); 5 | 6 | @override 7 | List get props => []; 8 | } 9 | 10 | final class AssigneeSelectedEvent extends AssigneeEvent { 11 | const AssigneeSelectedEvent({ 12 | required this.userId, 13 | this.selfAssignment = false, 14 | }); 15 | 16 | final String userId; 17 | final bool selfAssignment; 18 | 19 | @override 20 | List get props => [userId, selfAssignment]; 21 | } 22 | 23 | final class AssigneeSearchEvent extends AssigneeEvent { 24 | const AssigneeSearchEvent({required this.searchText}); 25 | 26 | final String searchText; 27 | 28 | @override 29 | List get props => [searchText]; 30 | } 31 | 32 | final class AssigneeResetSearchTextEvent extends AssigneeEvent { 33 | const AssigneeResetSearchTextEvent(); 34 | } 35 | 36 | final class AssigneeResetEvent extends AssigneeEvent { 37 | const AssigneeResetEvent(); 38 | } 39 | 40 | final class AssigneeRefreshEvent extends AssigneeEvent { 41 | const AssigneeRefreshEvent(); 42 | } 43 | 44 | final class AssigneeResetUnCommittedChanges extends AssigneeEvent { 45 | const AssigneeResetUnCommittedChanges(); 46 | } 47 | -------------------------------------------------------------------------------- /lib/modules/alarm/presentation/bloc/assignee/assignee_state.dart: -------------------------------------------------------------------------------- 1 | import 'package:equatable/equatable.dart'; 2 | import 'package:thingsboard_app/modules/alarm/domain/entities/assignee_entity.dart'; 3 | 4 | sealed class AssigneeState extends Equatable { 5 | const AssigneeState(); 6 | 7 | @override 8 | List get props => []; 9 | } 10 | 11 | final class AssigneeEmptyState extends AssigneeState { 12 | const AssigneeEmptyState(); 13 | } 14 | 15 | final class AssigneeSelectedState extends AssigneeState { 16 | const AssigneeSelectedState({required this.assignee}); 17 | 18 | final AssigneeEntity assignee; 19 | 20 | @override 21 | List get props => [assignee]; 22 | } 23 | 24 | final class AssigneeSelfAssignmentState extends AssigneeState { 25 | const AssigneeSelfAssignmentState(); 26 | } 27 | -------------------------------------------------------------------------------- /lib/modules/alarm/presentation/bloc/assignee/bloc.dart: -------------------------------------------------------------------------------- 1 | export 'assignee_bloc.dart'; 2 | export 'assignee_event.dart'; 3 | export 'assignee_state.dart'; 4 | -------------------------------------------------------------------------------- /lib/modules/alarm/presentation/bloc/bloc.dart: -------------------------------------------------------------------------------- 1 | export 'alarms_bloc.dart'; 2 | export 'alarms_events.dart'; 3 | export 'alarms_states.dart'; 4 | -------------------------------------------------------------------------------- /lib/modules/alarm/presentation/bloc/filters/filters/alarm_assignee_filter.dart: -------------------------------------------------------------------------------- 1 | import 'package:thingsboard_app/core/logger/tb_logger.dart'; 2 | import 'package:thingsboard_app/modules/alarm/presentation/bloc/filters/filters/i_alarm_filter.dart'; 3 | 4 | class AlarmAssigneeFilter implements IAlarmFilter { 5 | AlarmAssigneeFilter({required this.logger, T? initiallySelected}) { 6 | selectedUsed = initiallySelected; 7 | } 8 | 9 | T? selectedUsed; 10 | final TbLogger logger; 11 | 12 | @override 13 | T? getSelectedFilterData() { 14 | logger.debug( 15 | 'AlarmAssigneeFilter::getSelectedFilterData() -> $selectedUsed', 16 | ); 17 | 18 | return selectedUsed; 19 | } 20 | 21 | @override 22 | void updateSelectedData(data) { 23 | logger.debug( 24 | 'AlarmAssigneeFilter::updateSelectedData($data)', 25 | ); 26 | 27 | selectedUsed = data; 28 | } 29 | 30 | @override 31 | void reset() { 32 | selectedUsed = null; 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /lib/modules/alarm/presentation/bloc/filters/filters/alarm_severity_filter.dart: -------------------------------------------------------------------------------- 1 | import 'package:thingsboard_app/core/logger/tb_logger.dart'; 2 | import 'package:thingsboard_app/modules/alarm/presentation/bloc/filters/filters/i_alarm_filter.dart'; 3 | 4 | class AlarmSeverityFilter implements IAlarmFilter { 5 | AlarmSeverityFilter({required this.logger, T? initiallySelected}) { 6 | if (initiallySelected != null) { 7 | alarmSeveritySelected.add(initiallySelected); 8 | } 9 | } 10 | 11 | final alarmSeveritySelected = {}; 12 | final TbLogger logger; 13 | 14 | @override 15 | Set getSelectedFilterData() { 16 | logger.debug( 17 | 'AlarmSeverityFilter::getSelectedFilterData() -> $alarmSeveritySelected', 18 | ); 19 | 20 | return Set.of(alarmSeveritySelected); 21 | } 22 | 23 | @override 24 | void updateSelectedData(data) { 25 | logger.debug( 26 | 'AlarmStatusFilter::updateSelectedData($data)', 27 | ); 28 | 29 | alarmSeveritySelected 30 | ..clear() 31 | ..addAll(data); 32 | } 33 | 34 | @override 35 | void reset() { 36 | alarmSeveritySelected.clear(); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /lib/modules/alarm/presentation/bloc/filters/filters/alarm_status_filter.dart: -------------------------------------------------------------------------------- 1 | import 'package:thingsboard_app/core/logger/tb_logger.dart'; 2 | import 'package:thingsboard_app/modules/alarm/presentation/bloc/filters/filters/i_alarm_filter.dart'; 3 | 4 | class AlarmStatusFilter implements IAlarmFilter { 5 | AlarmStatusFilter({required this.logger, this.initiallySelected}) { 6 | if (initiallySelected != null) { 7 | alarmStatusSelected.add(initiallySelected as T); 8 | } 9 | } 10 | 11 | final alarmStatusSelected = {}; 12 | final TbLogger logger; 13 | T? initiallySelected; 14 | 15 | @override 16 | Set getSelectedFilterData() { 17 | logger.debug( 18 | 'AlarmStatusFilter::getSelectedFilterData() -> $alarmStatusSelected', 19 | ); 20 | 21 | return Set.of(alarmStatusSelected); 22 | } 23 | 24 | @override 25 | void updateSelectedData(data) { 26 | logger.debug( 27 | 'AlarmStatusFilter::updateSelectedData($data)', 28 | ); 29 | 30 | alarmStatusSelected 31 | ..clear() 32 | ..addAll(data); 33 | } 34 | 35 | @override 36 | void reset() { 37 | alarmStatusSelected.clear(); 38 | if (initiallySelected != null) { 39 | alarmStatusSelected.add(initiallySelected as T); 40 | } 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /lib/modules/alarm/presentation/bloc/filters/filters/alarm_type_filter.dart: -------------------------------------------------------------------------------- 1 | import 'package:thingsboard_app/core/logger/tb_logger.dart'; 2 | import 'package:thingsboard_app/modules/alarm/presentation/bloc/filters/filters/i_alarm_filter.dart'; 3 | 4 | class AlarmTypeFilter implements IAlarmFilter { 5 | AlarmTypeFilter({required this.logger, T? initiallySelected}) { 6 | if (initiallySelected != null) { 7 | alarmTypeSelected.add(initiallySelected); 8 | } 9 | } 10 | 11 | final alarmTypeSelected = {}; 12 | final TbLogger logger; 13 | 14 | @override 15 | Set getSelectedFilterData() { 16 | logger.debug( 17 | 'AlarmTypeFilter::getSelectedFilterData() -> $alarmTypeSelected', 18 | ); 19 | 20 | return Set.of(alarmTypeSelected); 21 | } 22 | 23 | @override 24 | void updateSelectedData(data) { 25 | logger.debug( 26 | 'AlarmTypeFilter::updateSelectedData($data)', 27 | ); 28 | 29 | alarmTypeSelected 30 | ..clear() 31 | ..addAll(data); 32 | } 33 | 34 | @override 35 | void reset() { 36 | alarmTypeSelected.clear(); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /lib/modules/alarm/presentation/bloc/filters/filters/i_alarm_filter.dart: -------------------------------------------------------------------------------- 1 | abstract interface class IAlarmFilter { 2 | T getSelectedFilterData(); 3 | 4 | void updateSelectedData(T data); 5 | 6 | void reset(); 7 | } 8 | -------------------------------------------------------------------------------- /lib/modules/alarm/presentation/bloc/filters/i_alarm_filters_service.dart: -------------------------------------------------------------------------------- 1 | import 'package:thingsboard_app/modules/alarm/domain/entities/alarm_filters_entity.dart'; 2 | import 'package:thingsboard_app/modules/alarm/domain/entities/filter_data_entity.dart'; 3 | 4 | enum Filters { status, severity, type, assignee } 5 | 6 | abstract interface class IAlarmFiltersService { 7 | List get statuses; 8 | 9 | List get severities; 10 | 11 | AlarmFiltersEntity getCommittedFilters(); 12 | 13 | void commitChanges(); 14 | 15 | void reset(); 16 | 17 | void resetUnCommittedChanges(); 18 | 19 | T getSelectedFilter(Filters type); 20 | 21 | void setSelectedFilter(Filters type, {required T data}); 22 | } 23 | -------------------------------------------------------------------------------- /lib/modules/alarm/presentation/view/alarms_search_page.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:thingsboard_app/config/routes/router.dart'; 3 | import 'package:thingsboard_app/core/context/tb_context.dart'; 4 | import 'package:thingsboard_app/locator.dart'; 5 | import 'package:thingsboard_app/modules/alarm/alarms_list.dart'; 6 | import 'package:thingsboard_app/modules/alarm/presentation/bloc/bloc.dart'; 7 | import 'package:thingsboard_app/utils/ui/back_button_widget.dart'; 8 | import 'package:thingsboard_app/widgets/tb_app_bar.dart'; 9 | 10 | class AlarmsSearchPage extends StatelessWidget { 11 | const AlarmsSearchPage({ 12 | required this.tbContext, 13 | super.key, 14 | }); 15 | 16 | final TbContext tbContext; 17 | 18 | @override 19 | Widget build(BuildContext context) { 20 | return Scaffold( 21 | appBar: TbAppSearchBar( 22 | tbContext, 23 | onSearch: (searchText) => getIt().add( 24 | AlarmSearchTextChanged( 25 | searchText: searchText, 26 | ), 27 | ), 28 | leading: BackButtonWidget( 29 | onPressed: () { 30 | getIt().add( 31 | const AlarmSearchTextChanged( 32 | searchText: null, 33 | ), 34 | ); 35 | 36 | getIt().router.pop(context); 37 | }, 38 | ), 39 | ), 40 | body: AlarmsList(tbContext: tbContext), 41 | ); 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /lib/modules/alarm/presentation/widgets/activity/activity_builder_widget.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:thingsboard_app/modules/alarm/presentation/widgets/activity/system_activity_widget.dart'; 3 | import 'package:thingsboard_app/modules/alarm/presentation/widgets/activity/user_comment_widget.dart'; 4 | import 'package:thingsboard_app/thingsboard_client.dart'; 5 | 6 | class ActivityBuilderWidget extends StatelessWidget { 7 | const ActivityBuilderWidget(this.activity, {required this.userId, super.key}); 8 | 9 | final AlarmCommentInfo activity; 10 | final UserId userId; 11 | 12 | @override 13 | Widget build(BuildContext context) { 14 | if (activity.type == AlarmCommentType.OTHER) { 15 | return UserCommentWidget(activity, userId: userId); 16 | } 17 | 18 | return SystemActivityWidget(activity); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /lib/modules/alarm/presentation/widgets/activity/system_activity_widget.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:thingsboard_app/thingsboard_client.dart'; 3 | import 'package:thingsboard_app/utils/ui/tb_text_styles.dart'; 4 | import 'package:timeago/timeago.dart' as timeago; 5 | 6 | class SystemActivityWidget extends StatelessWidget { 7 | const SystemActivityWidget(this.activity, {super.key}); 8 | 9 | final AlarmCommentInfo activity; 10 | 11 | @override 12 | Widget build(BuildContext context) { 13 | final diff = DateTime.now().difference( 14 | DateTime.fromMillisecondsSinceEpoch(activity.createdTime), 15 | ); 16 | 17 | return Column( 18 | crossAxisAlignment: CrossAxisAlignment.start, 19 | children: [ 20 | Text( 21 | timeago.format(DateTime.now().subtract(diff)), 22 | style: TbTextStyles.labelMedium.copyWith( 23 | color: Colors.black.withOpacity(.38), 24 | ), 25 | ), 26 | Text( 27 | activity.comment.text, 28 | style: TbTextStyles.bodyLarge.copyWith( 29 | color: Colors.black.withOpacity(.54), 30 | ), 31 | ), 32 | ], 33 | ); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /lib/modules/alarm/presentation/widgets/assignee/user_info_avatar_widget.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:google_fonts/google_fonts.dart'; 3 | 4 | class UserInfoAvatarWidget extends StatelessWidget { 5 | const UserInfoAvatarWidget({ 6 | required this.shortName, 7 | required this.color, 8 | super.key, 9 | }) : assert(shortName.length <= 2, 'shortName is $shortName'); 10 | 11 | final String shortName; 12 | final Color color; 13 | 14 | @override 15 | Widget build(BuildContext context) { 16 | return Container( 17 | decoration: BoxDecoration( 18 | borderRadius: BorderRadius.circular(100), 19 | color: color, 20 | ), 21 | padding: const EdgeInsets.all(4), 22 | height: 32, 23 | width: 32, 24 | child: Center( 25 | child: Text( 26 | shortName, 27 | style: GoogleFonts.roboto( 28 | fontWeight: FontWeight.w700, 29 | fontSize: 14, 30 | letterSpacing: 1, 31 | height: 1.14, 32 | color: Colors.white, 33 | ), 34 | ), 35 | ), 36 | ); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /lib/modules/alarm/presentation/widgets/details/alarm_status_button.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:thingsboard_app/utils/ui/tb_text_styles.dart'; 3 | 4 | class AlarmStatusButton extends StatelessWidget { 5 | const AlarmStatusButton({required this.text, required this.onTap, super.key}); 6 | 7 | final String text; 8 | final VoidCallback? onTap; 9 | 10 | @override 11 | Widget build(BuildContext context) { 12 | return ElevatedButton( 13 | onPressed: onTap, 14 | child: Padding( 15 | padding: const EdgeInsets.all(10), 16 | child: Text( 17 | text, 18 | style: TbTextStyles.titleXs, 19 | maxLines: 1, 20 | overflow: TextOverflow.ellipsis, 21 | ), 22 | ), 23 | ); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /lib/modules/asset/asset_routes.dart: -------------------------------------------------------------------------------- 1 | import 'package:fluro/fluro.dart'; 2 | import 'package:flutter/widgets.dart'; 3 | import 'package:thingsboard_app/config/routes/router.dart'; 4 | import 'package:thingsboard_app/core/context/tb_context.dart'; 5 | import 'package:thingsboard_app/modules/asset/assets_page.dart'; 6 | 7 | import 'asset_details_page.dart'; 8 | 9 | class AssetRoutes extends TbRoutes { 10 | late var assetsHandler = Handler( 11 | handlerFunc: (BuildContext? context, Map params) { 12 | var searchMode = params['search']?.first == 'true'; 13 | return AssetsPage(tbContext, searchMode: searchMode); 14 | }, 15 | ); 16 | 17 | late var assetDetailsHandler = Handler( 18 | handlerFunc: (BuildContext? context, Map params) { 19 | return AssetDetailsPage(tbContext, params['id'][0]); 20 | }, 21 | ); 22 | 23 | AssetRoutes(TbContext tbContext) : super(tbContext); 24 | 25 | @override 26 | void doRegisterRoutes(router) { 27 | router.define('/assets', handler: assetsHandler); 28 | router.define('/asset/:id', handler: assetDetailsHandler); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /lib/modules/asset/assets_list.dart: -------------------------------------------------------------------------------- 1 | import 'package:thingsboard_app/core/context/tb_context.dart'; 2 | import 'package:thingsboard_app/core/entity/entities_base.dart'; 3 | import 'package:thingsboard_app/core/entity/entities_list.dart'; 4 | import 'package:thingsboard_app/thingsboard_client.dart'; 5 | 6 | import 'assets_base.dart'; 7 | 8 | class AssetsList extends BaseEntitiesWidget 9 | with AssetsBase, EntitiesListStateBase { 10 | AssetsList( 11 | TbContext tbContext, 12 | PageKeyController pageKeyController, { 13 | super.key, 14 | searchMode = false, 15 | }) : super(tbContext, pageKeyController, searchMode: searchMode); 16 | } 17 | -------------------------------------------------------------------------------- /lib/modules/asset/assets_list_widget.dart: -------------------------------------------------------------------------------- 1 | import 'package:thingsboard_app/core/context/tb_context.dart'; 2 | import 'package:thingsboard_app/core/entity/entities_list_widget.dart'; 3 | import 'package:thingsboard_app/modules/asset/assets_base.dart'; 4 | import 'package:thingsboard_app/thingsboard_client.dart'; 5 | 6 | class AssetsListWidget extends EntitiesListPageLinkWidget 7 | with AssetsBase { 8 | AssetsListWidget( 9 | TbContext tbContext, { 10 | super.key, 11 | EntitiesListWidgetController? controller, 12 | }) : super(tbContext, controller: controller); 13 | 14 | @override 15 | void onViewAll() { 16 | navigateTo('/assets'); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /lib/modules/audit_log/audit_logs_list.dart: -------------------------------------------------------------------------------- 1 | import 'package:thingsboard_app/core/context/tb_context.dart'; 2 | import 'package:thingsboard_app/core/entity/entities_base.dart'; 3 | import 'package:thingsboard_app/core/entity/entities_list.dart'; 4 | import 'package:thingsboard_app/modules/audit_log/audit_logs_base.dart'; 5 | import 'package:thingsboard_app/thingsboard_client.dart'; 6 | 7 | class AuditLogsList extends BaseEntitiesWidget 8 | with AuditLogsBase, EntitiesListStateBase { 9 | AuditLogsList( 10 | TbContext tbContext, 11 | PageKeyController pageKeyController, { 12 | searchMode = false, 13 | super.key, 14 | }) : super(tbContext, pageKeyController, searchMode: searchMode); 15 | } 16 | -------------------------------------------------------------------------------- /lib/modules/audit_log/audit_logs_routes.dart: -------------------------------------------------------------------------------- 1 | import 'package:fluro/fluro.dart'; 2 | import 'package:flutter/widgets.dart'; 3 | import 'package:thingsboard_app/config/routes/router.dart'; 4 | import 'package:thingsboard_app/core/context/tb_context.dart'; 5 | import 'package:thingsboard_app/modules/audit_log/audit_logs_page.dart'; 6 | 7 | class AuditLogsRoutes extends TbRoutes { 8 | late final auditLogsHandler = Handler( 9 | handlerFunc: (BuildContext? context, Map params) { 10 | var searchMode = params['search']?.first == 'true'; 11 | return AuditLogsPage(tbContext, searchMode: searchMode); 12 | }, 13 | ); 14 | 15 | AuditLogsRoutes(TbContext tbContext) : super(tbContext); 16 | 17 | @override 18 | void doRegisterRoutes(router) { 19 | router.define('/auditLogs', handler: auditLogsHandler); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /lib/modules/customer/customer_details_page.dart: -------------------------------------------------------------------------------- 1 | import 'package:thingsboard_app/core/context/tb_context.dart'; 2 | import 'package:thingsboard_app/core/entity/entity_details_page.dart'; 3 | import 'package:thingsboard_app/thingsboard_client.dart'; 4 | 5 | class CustomerDetailsPage extends ContactBasedDetailsPage { 6 | CustomerDetailsPage(TbContext tbContext, String customerId, {super.key}) 7 | : super( 8 | tbContext, 9 | entityId: customerId, 10 | defaultTitle: 'Customer', 11 | subTitle: 'Customer details', 12 | ); 13 | 14 | @override 15 | Future fetchEntity(String id) { 16 | return tbClient.getCustomerService().getCustomer(id); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /lib/modules/customer/customer_routes.dart: -------------------------------------------------------------------------------- 1 | import 'package:fluro/fluro.dart'; 2 | import 'package:flutter/widgets.dart'; 3 | import 'package:thingsboard_app/config/routes/router.dart'; 4 | import 'package:thingsboard_app/core/context/tb_context.dart'; 5 | 6 | import 'customer_details_page.dart'; 7 | import 'customers_page.dart'; 8 | 9 | class CustomerRoutes extends TbRoutes { 10 | late final customersHandler = Handler( 11 | handlerFunc: (BuildContext? context, Map params) { 12 | var searchMode = params['search']?.first == 'true'; 13 | return CustomersPage(tbContext, searchMode: searchMode); 14 | }, 15 | ); 16 | 17 | late final customerDetailsHandler = Handler( 18 | handlerFunc: (BuildContext? context, Map params) { 19 | return CustomerDetailsPage(tbContext, params['id'][0]); 20 | }, 21 | ); 22 | 23 | CustomerRoutes(TbContext tbContext) : super(tbContext); 24 | 25 | @override 26 | void doRegisterRoutes(router) { 27 | router.define('/customers', handler: customersHandler); 28 | router.define('/customer/:id', handler: customerDetailsHandler); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /lib/modules/customer/customers_base.dart: -------------------------------------------------------------------------------- 1 | import 'package:thingsboard_app/core/entity/entities_base.dart'; 2 | import 'package:thingsboard_app/thingsboard_client.dart'; 3 | 4 | mixin CustomersBase on EntitiesBase { 5 | @override 6 | String get title => 'Customers'; 7 | 8 | @override 9 | String get noItemsFoundText => 'No customers found'; 10 | 11 | @override 12 | Future> fetchEntities(PageLink pageLink) { 13 | return tbClient.getCustomerService().getCustomers(pageLink); 14 | } 15 | 16 | @override 17 | void onEntityTap(Customer customer) { 18 | navigateTo('/customer/${customer.id!.id}'); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /lib/modules/customer/customers_list.dart: -------------------------------------------------------------------------------- 1 | import 'package:thingsboard_app/core/context/tb_context.dart'; 2 | import 'package:thingsboard_app/core/entity/entities_base.dart'; 3 | import 'package:thingsboard_app/core/entity/entities_list.dart'; 4 | import 'package:thingsboard_app/thingsboard_client.dart'; 5 | 6 | import 'customers_base.dart'; 7 | 8 | class CustomersList extends BaseEntitiesWidget 9 | with CustomersBase, ContactBasedBase, EntitiesListStateBase { 10 | CustomersList( 11 | TbContext tbContext, 12 | PageKeyController pageKeyController, { 13 | super.key, 14 | searchMode = false, 15 | }) : super(tbContext, pageKeyController, searchMode: searchMode); 16 | } 17 | -------------------------------------------------------------------------------- /lib/modules/dashboard/di/dashboards_di.dart: -------------------------------------------------------------------------------- 1 | import 'package:thingsboard_app/locator.dart'; 2 | import 'package:thingsboard_app/modules/dashboard/domain/pagination/dashboards_pagination_repository.dart'; 3 | import 'package:thingsboard_app/modules/dashboard/domain/pagination/dashboards_query_ctrl.dart'; 4 | import 'package:thingsboard_app/modules/dashboard/domain/usecases/fetch_dashboards_usecase.dart'; 5 | import 'package:thingsboard_app/thingsboard_client.dart'; 6 | 7 | abstract class DashboardsDi { 8 | static void init( 9 | String key, { 10 | required ThingsboardClient tbClient, 11 | }) { 12 | getIt.pushNewScope( 13 | scopeName: key, 14 | init: (locator) { 15 | locator.registerLazySingleton( 16 | () => DashboardsQueryCtrl(), 17 | ); 18 | 19 | locator.registerFactory( 20 | () => FetchDashboardsUseCase(tbClient), 21 | ); 22 | 23 | locator.registerLazySingleton( 24 | () => DashboardsPaginationRepository( 25 | queryController: locator(), 26 | onFetchData: locator(), 27 | ), 28 | ); 29 | }, 30 | ); 31 | } 32 | 33 | static void dispose(String scopeName) { 34 | getIt.dropScope(scopeName); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /lib/modules/dashboard/domain/entites/dashboard_arguments.dart: -------------------------------------------------------------------------------- 1 | import 'package:equatable/equatable.dart'; 2 | 3 | /// This class represents the arguments a user can pass to open a dashboard 4 | class DashboardArgumentsEntity extends Equatable { 5 | const DashboardArgumentsEntity( 6 | this.id, { 7 | this.title, 8 | this.state, 9 | this.hideToolbar, 10 | this.animate = true, 11 | }); 12 | 13 | final String id; 14 | final String? title; 15 | final String? state; 16 | final bool? hideToolbar; 17 | final bool animate; 18 | 19 | @override 20 | List get props => [id, title, state, hideToolbar, animate]; 21 | } 22 | -------------------------------------------------------------------------------- /lib/modules/dashboard/domain/pagination/dashboards_pagination_repository.dart: -------------------------------------------------------------------------------- 1 | import 'package:thingsboard_app/modules/dashboard/domain/pagination/dashboards_query_ctrl.dart'; 2 | import 'package:thingsboard_app/thingsboard_client.dart'; 3 | import 'package:thingsboard_app/utils/services/pagination_repository.dart'; 4 | 5 | final class DashboardsPaginationRepository 6 | extends PaginationRepository { 7 | DashboardsPaginationRepository({ 8 | required DashboardsQueryCtrl queryController, 9 | required this.onFetchData, 10 | }) : super(pageKeyController: queryController); 11 | 12 | final Future> Function(PageLink query) onFetchData; 13 | 14 | @override 15 | Future> fetchPageData(PageLink pageKey) { 16 | return onFetchData(pageKey); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /lib/modules/dashboard/domain/pagination/dashboards_query_ctrl.dart: -------------------------------------------------------------------------------- 1 | import 'package:thingsboard_app/core/entity/entities_base.dart'; 2 | import 'package:thingsboard_app/thingsboard_client.dart'; 3 | 4 | class DashboardsQueryCtrl extends PageKeyController { 5 | DashboardsQueryCtrl({int pageSize = 20}) : super(PageLink(20)); 6 | 7 | @override 8 | PageLink nextPageKey(PageLink pageKey) { 9 | return pageKey.nextPageLink(); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /lib/modules/dashboard/domain/usecases/fetch_dashboards_usecase.dart: -------------------------------------------------------------------------------- 1 | import 'package:thingsboard_app/thingsboard_client.dart'; 2 | import 'package:thingsboard_app/utils/usecase.dart'; 3 | 4 | class FetchDashboardsUseCase 5 | extends UseCase>, PageLink> { 6 | const FetchDashboardsUseCase(this.tbClient); 7 | 8 | final ThingsboardClient tbClient; 9 | 10 | @override 11 | Future> call(PageLink params) { 12 | if (tbClient.isTenantAdmin()) { 13 | return tbClient.getDashboardService().getTenantDashboards( 14 | params, 15 | mobile: true, 16 | ); 17 | } else { 18 | return tbClient.getDashboardService().getCustomerDashboards( 19 | tbClient.getAuthUser()!.customerId!, 20 | params, 21 | mobile: true, 22 | ); 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /lib/modules/dashboard/presentation/controller/dashboard_page_controller.dart: -------------------------------------------------------------------------------- 1 | import 'dart:async'; 2 | 3 | import 'package:flutter/foundation.dart'; 4 | import 'package:thingsboard_app/modules/dashboard/presentation/controller/dashboard_controller.dart'; 5 | import 'package:thingsboard_app/widgets/two_page_view.dart'; 6 | 7 | class DashboardPageController { 8 | DashboardPageController({required this.pageCtrl}); 9 | 10 | final dashboardController = Completer(); 11 | late ValueNotifier dashboardTitleValue; 12 | final TwoPageViewController pageCtrl; 13 | 14 | void setDashboardController(DashboardController controller) { 15 | dashboardController.complete(controller); 16 | } 17 | 18 | void setDashboardTitleNotifier(ValueNotifier notifier) { 19 | dashboardTitleValue = notifier; 20 | } 21 | 22 | Future openDashboard( 23 | String dashboardId, { 24 | String? title, 25 | String? state, 26 | bool? hideToolbar, 27 | }) async { 28 | if (title != null) { 29 | dashboardTitleValue.value = title; 30 | } 31 | 32 | dashboardController.future.then((controller) { 33 | controller.openDashboard( 34 | dashboardId, 35 | state: state, 36 | hideToolbar: hideToolbar, 37 | ); 38 | }); 39 | 40 | return pageCtrl.open(1, animate: true); 41 | } 42 | 43 | Future closeDashboard() async { 44 | return pageCtrl.close(1, animate: true); 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /lib/modules/dashboard/presentation/view/home_dashboard_page.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:thingsboard_app/core/context/tb_context.dart'; 3 | import 'package:thingsboard_app/core/context/tb_context_widget.dart'; 4 | import 'package:thingsboard_app/modules/dashboard/presentation/widgets/dashboard_widget.dart'; 5 | import 'package:thingsboard_app/modules/dashboard/presentation/widgets/dashboards_appbar.dart'; 6 | import 'package:thingsboard_app/thingsboard_client.dart'; 7 | 8 | class HomeDashboardPage extends TbContextWidget { 9 | final HomeDashboardInfo dashboard; 10 | 11 | HomeDashboardPage(TbContext tbContext, this.dashboard, {super.key}) 12 | : super(tbContext); 13 | 14 | @override 15 | State createState() => _HomeDashboardState(); 16 | } 17 | 18 | class _HomeDashboardState extends TbContextState { 19 | @override 20 | Widget build(BuildContext context) { 21 | return DashboardsAppbar( 22 | tbContext: tbContext, 23 | dashboardState: true, 24 | body: DashboardWidget( 25 | tbContext, 26 | home: true, 27 | controllerCallback: (controller, _) { 28 | controller.openDashboard( 29 | widget.dashboard.dashboardId!.id!, 30 | hideToolbar: widget.dashboard.hideDashboardToolbar, 31 | home: true, 32 | ); 33 | }, 34 | ), 35 | ); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /lib/modules/device/device_details_page.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:thingsboard_app/core/context/tb_context.dart'; 3 | import 'package:thingsboard_app/core/entity/entity_details_page.dart'; 4 | import 'package:thingsboard_app/thingsboard_client.dart'; 5 | 6 | class DeviceDetailsPage extends EntityDetailsPage { 7 | DeviceDetailsPage(TbContext tbContext, String deviceId, {super.key}) 8 | : super(tbContext, entityId: deviceId, defaultTitle: 'Device'); 9 | 10 | @override 11 | Future fetchEntity(String id) { 12 | return tbClient.getDeviceService().getDeviceInfo(id); 13 | } 14 | 15 | @override 16 | Widget buildEntityDetails(BuildContext context, DeviceInfo entity) { 17 | return ListTile( 18 | title: Text(entity.name), 19 | subtitle: Text(entity.type), 20 | ); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /lib/modules/device/device_profiles_grid.dart: -------------------------------------------------------------------------------- 1 | import 'package:thingsboard_app/core/context/tb_context.dart'; 2 | import 'package:thingsboard_app/core/entity/entities_base.dart'; 3 | import 'package:thingsboard_app/core/entity/entities_grid.dart'; 4 | import 'package:thingsboard_app/thingsboard_client.dart'; 5 | 6 | import 'device_profiles_base.dart'; 7 | 8 | class DeviceProfilesGrid extends BaseEntitiesWidget 9 | with DeviceProfilesBase, EntitiesGridStateBase { 10 | DeviceProfilesGrid( 11 | TbContext tbContext, 12 | PageKeyController pageKeyController, { 13 | super.key, 14 | }) : super(tbContext, pageKeyController); 15 | } 16 | -------------------------------------------------------------------------------- /lib/modules/device/devices_list.dart: -------------------------------------------------------------------------------- 1 | import 'package:thingsboard_app/core/context/tb_context.dart'; 2 | import 'package:thingsboard_app/core/entity/entities_base.dart'; 3 | import 'package:thingsboard_app/core/entity/entities_list.dart'; 4 | import 'package:thingsboard_app/modules/device/devices_base.dart'; 5 | import 'package:thingsboard_app/thingsboard_client.dart'; 6 | 7 | class DevicesList extends BaseEntitiesWidget 8 | with DevicesBase, EntitiesListStateBase { 9 | final bool displayDeviceImage; 10 | 11 | DevicesList( 12 | TbContext tbContext, 13 | PageKeyController pageKeyController, { 14 | super.key, 15 | searchMode = false, 16 | this.displayDeviceImage = false, 17 | }) : super(tbContext, pageKeyController, searchMode: searchMode); 18 | 19 | @override 20 | bool displayCardImage(bool listWidgetCard) => displayDeviceImage; 21 | } 22 | -------------------------------------------------------------------------------- /lib/modules/device/devices_list_widget.dart: -------------------------------------------------------------------------------- 1 | import 'package:thingsboard_app/core/context/tb_context.dart'; 2 | import 'package:thingsboard_app/core/entity/entities_base.dart'; 3 | import 'package:thingsboard_app/core/entity/entities_list_widget.dart'; 4 | import 'package:thingsboard_app/modules/device/devices_base.dart'; 5 | import 'package:thingsboard_app/thingsboard_client.dart'; 6 | 7 | class DevicesListWidget extends EntitiesListWidget 8 | with DevicesBase { 9 | DevicesListWidget( 10 | TbContext tbContext, { 11 | super.key, 12 | EntitiesListWidgetController? controller, 13 | }) : super(tbContext, controller: controller); 14 | 15 | @override 16 | void onViewAll() { 17 | navigateTo('/devices'); 18 | } 19 | 20 | @override 21 | PageKeyController createPageKeyController() => 22 | DeviceQueryController(pageSize: 5); 23 | } 24 | -------------------------------------------------------------------------------- /lib/modules/device/devices_main_page.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:thingsboard_app/core/context/tb_context.dart'; 3 | import 'package:thingsboard_app/core/context/tb_context_widget.dart'; 4 | import 'package:thingsboard_app/core/entity/entities_base.dart'; 5 | import 'package:thingsboard_app/modules/device/device_profiles_grid.dart'; 6 | import 'package:thingsboard_app/widgets/tb_app_bar.dart'; 7 | 8 | class DevicesMainPage extends TbContextWidget { 9 | DevicesMainPage(TbContext tbContext, {super.key}) : super(tbContext); 10 | 11 | @override 12 | State createState() => _DevicesMainPageState(); 13 | } 14 | 15 | class _DevicesMainPageState extends TbContextState 16 | with AutomaticKeepAliveClientMixin { 17 | final PageLinkController _pageLinkController = PageLinkController(); 18 | 19 | @override 20 | bool get wantKeepAlive { 21 | return true; 22 | } 23 | 24 | @override 25 | Widget build(BuildContext context) { 26 | super.build(context); 27 | final deviceProfilesList = DeviceProfilesGrid( 28 | tbContext, 29 | _pageLinkController, 30 | ); 31 | 32 | return Scaffold( 33 | appBar: TbAppBar( 34 | tbContext, 35 | title: Text(deviceProfilesList.title), 36 | ), 37 | body: deviceProfilesList, 38 | ); 39 | } 40 | 41 | @override 42 | void dispose() { 43 | _pageLinkController.dispose(); 44 | super.dispose(); 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /lib/modules/device/devices_page.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:thingsboard_app/core/context/tb_context.dart'; 3 | import 'package:thingsboard_app/core/context/tb_context_widget.dart'; 4 | import 'package:thingsboard_app/core/entity/entities_base.dart'; 5 | import 'package:thingsboard_app/modules/device/device_profiles_grid.dart'; 6 | import 'package:thingsboard_app/widgets/tb_app_bar.dart'; 7 | 8 | class DevicesPage extends TbPageWidget { 9 | DevicesPage(TbContext tbContext, {super.key}) : super(tbContext); 10 | 11 | @override 12 | State createState() => _DevicesPageState(); 13 | } 14 | 15 | class _DevicesPageState extends TbPageState { 16 | final PageLinkController _pageLinkController = PageLinkController(); 17 | 18 | @override 19 | Widget build(BuildContext context) { 20 | final deviceProfilesList = DeviceProfilesGrid( 21 | tbContext, 22 | _pageLinkController, 23 | ); 24 | return Scaffold( 25 | appBar: TbAppBar( 26 | tbContext, 27 | title: Text(deviceProfilesList.title), 28 | ), 29 | body: deviceProfilesList, 30 | ); 31 | } 32 | 33 | @override 34 | void dispose() { 35 | _pageLinkController.dispose(); 36 | super.dispose(); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /lib/modules/device/provisioning/ble/bloc/bloc.dart: -------------------------------------------------------------------------------- 1 | export 'eps_ble_provisioning_bloc.dart'; 2 | export 'events.dart'; 3 | export 'states.dart'; 4 | -------------------------------------------------------------------------------- /lib/modules/device/provisioning/ble/bloc/events.dart: -------------------------------------------------------------------------------- 1 | import 'package:equatable/equatable.dart'; 2 | 3 | sealed class EspBleProvisioningEvent extends Equatable { 4 | const EspBleProvisioningEvent(); 5 | 6 | @override 7 | List get props => []; 8 | } 9 | 10 | final class EspBleScanNetworksEvent extends EspBleProvisioningEvent { 11 | const EspBleScanNetworksEvent({ 12 | required this.deviceName, 13 | required this.pop, 14 | }); 15 | 16 | final String deviceName; 17 | final String pop; // proofOfPossession 18 | 19 | @override 20 | List get props => [pop, deviceName]; 21 | } 22 | 23 | final class EspBleProvisionDeviceEvent extends EspBleProvisioningEvent { 24 | const EspBleProvisionDeviceEvent({ 25 | required this.device, 26 | required this.pop, 27 | required this.ssid, 28 | required this.pass, 29 | }); 30 | 31 | final String device; 32 | final String pop; 33 | final String ssid; 34 | final String pass; 35 | 36 | @override 37 | List get props => [device, pop, ssid, pass]; 38 | } 39 | 40 | final class EspBleProvisioningDoneEvent extends EspBleProvisioningEvent { 41 | const EspBleProvisioningDoneEvent(); 42 | } 43 | -------------------------------------------------------------------------------- /lib/modules/device/provisioning/ble/di/esp_ble_di.dart: -------------------------------------------------------------------------------- 1 | import 'package:thingsboard_app/locator.dart'; 2 | import 'package:thingsboard_app/utils/services/provisioning/eps_ble/i_wifi_provisioning_service.dart'; 3 | import 'package:thingsboard_app/utils/services/provisioning/eps_ble/wifi_provisioning_service.dart'; 4 | 5 | abstract final class EspBleDi { 6 | static void init(String scopeName) { 7 | getIt.pushNewScope( 8 | scopeName: scopeName, 9 | init: (locator) { 10 | locator.registerFactory( 11 | () => BleProvisioningService(), 12 | ); 13 | }, 14 | ); 15 | } 16 | 17 | static void dispose(String scopeName) { 18 | getIt.dropScope(scopeName); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /lib/modules/device/provisioning/ble/view/cannot_establish_session_view.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter_svg/flutter_svg.dart'; 3 | import 'package:thingsboard_app/constants/assets_path.dart'; 4 | import 'package:thingsboard_app/modules/device/provisioning/widgets/try_again_button.dart'; 5 | import 'package:thingsboard_app/utils/ui/tb_text_styles.dart'; 6 | 7 | class CannotEstablishSessionView extends StatelessWidget { 8 | const CannotEstablishSessionView({ 9 | required this.onTryAgain, 10 | required this.deviceName, 11 | super.key, 12 | }); 13 | 14 | final VoidCallback onTryAgain; 15 | final String deviceName; 16 | 17 | @override 18 | Widget build(BuildContext context) { 19 | return Column( 20 | children: [ 21 | SvgPicture.asset( 22 | ThingsboardImage.provisioningError, 23 | width: 140, 24 | height: 140, 25 | ), 26 | const SizedBox(height: 16), 27 | Text( 28 | deviceName, 29 | textAlign: TextAlign.center, 30 | style: TbTextStyles.bodyMedium.copyWith( 31 | color: Colors.black.withOpacity(.54), 32 | ), 33 | ), 34 | const Spacer(), 35 | TryAgainButton(onTryAgain: onTryAgain), 36 | ], 37 | ); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /lib/modules/device/provisioning/bloc/bloc.dart: -------------------------------------------------------------------------------- 1 | export 'device_provisioning_bloc.dart'; 2 | export 'device_provisioning_events.dart'; 3 | export 'device_provisioning_states.dart'; 4 | 5 | enum DeviceProvisioningStatus { idle, wifi, confirmation, success, fail, done } 6 | -------------------------------------------------------------------------------- /lib/modules/device/provisioning/bloc/device_provisioning_events.dart: -------------------------------------------------------------------------------- 1 | import 'package:equatable/equatable.dart'; 2 | 3 | sealed class DeviceProvisioningEvent extends Equatable { 4 | const DeviceProvisioningEvent(); 5 | 6 | @override 7 | List get props => []; 8 | } 9 | 10 | final class SendWifiCredentialsEvent extends DeviceProvisioningEvent { 11 | const SendWifiCredentialsEvent(); 12 | } 13 | 14 | final class ConfirmConnectionEvent extends DeviceProvisioningEvent { 15 | const ConfirmConnectionEvent(); 16 | } 17 | 18 | final class SuccessfullyProvisionedEvent extends DeviceProvisioningEvent { 19 | const SuccessfullyProvisionedEvent(); 20 | } 21 | 22 | final class ErrorDuringProvisioningEvent extends DeviceProvisioningEvent { 23 | const ErrorDuringProvisioningEvent(); 24 | } 25 | 26 | final class ProvisioningIdleEvent extends DeviceProvisioningEvent { 27 | const ProvisioningIdleEvent(); 28 | } 29 | 30 | final class ProceedWithClaimingEvent extends DeviceProvisioningEvent { 31 | const ProceedWithClaimingEvent(); 32 | } 33 | -------------------------------------------------------------------------------- /lib/modules/device/provisioning/soft_ap/bloc/bloc.dart: -------------------------------------------------------------------------------- 1 | export 'esp_softap_bloc.dart'; 2 | export 'esp_softap_events.dart'; 3 | export 'esp_softap_states.dart'; 4 | -------------------------------------------------------------------------------- /lib/modules/device/provisioning/soft_ap/bloc/esp_softap_events.dart: -------------------------------------------------------------------------------- 1 | import 'package:equatable/equatable.dart'; 2 | 3 | sealed class EspSoftApEvent extends Equatable { 4 | const EspSoftApEvent(); 5 | 6 | @override 7 | List get props => []; 8 | } 9 | 10 | final class EspSoftApConnectToDeviceEvent extends EspSoftApEvent { 11 | const EspSoftApConnectToDeviceEvent(); 12 | 13 | @override 14 | List get props => [double.nan]; 15 | } 16 | 17 | final class EspSoftApRescanWifiEvent extends EspSoftApEvent { 18 | const EspSoftApRescanWifiEvent(); 19 | } 20 | 21 | final class EspSoftApStartProvisioningEvent extends EspSoftApEvent { 22 | const EspSoftApStartProvisioningEvent({ 23 | required this.ssid, 24 | required this.password, 25 | }); 26 | 27 | final String ssid; 28 | final String password; 29 | 30 | @override 31 | List get props => [ssid, password]; 32 | } 33 | 34 | final class EspSoftApAutoConnectToDeviceWifi extends EspSoftApEvent { 35 | const EspSoftApAutoConnectToDeviceWifi(); 36 | } 37 | 38 | final class EspSoftApManuallyConnectToDeviceWifi extends EspSoftApEvent { 39 | const EspSoftApManuallyConnectToDeviceWifi(); 40 | } 41 | 42 | final class EspSoftApProvisioningDoneEvent extends EspSoftApEvent { 43 | const EspSoftApProvisioningDoneEvent(); 44 | } 45 | -------------------------------------------------------------------------------- /lib/modules/device/provisioning/soft_ap/bloc/esp_softap_states.dart: -------------------------------------------------------------------------------- 1 | import 'package:equatable/equatable.dart'; 2 | 3 | sealed class EspSoftApState extends Equatable { 4 | const EspSoftApState(); 5 | 6 | @override 7 | List get props => []; 8 | } 9 | 10 | final class EspSoftAppLoadingState extends EspSoftApState { 11 | const EspSoftAppLoadingState(); 12 | } 13 | 14 | final class EspSoftApConnectionErrorState extends EspSoftApState { 15 | const EspSoftApConnectionErrorState(); 16 | } 17 | 18 | final class EspSoftApWifiNetworksNotFoundState extends EspSoftApState { 19 | const EspSoftApWifiNetworksNotFoundState(); 20 | } 21 | 22 | final class EspManuallyConnectToDeviceNetworkState extends EspSoftApState { 23 | const EspManuallyConnectToDeviceNetworkState(); 24 | } 25 | 26 | final class EspSoftApWiFiListState extends EspSoftApState { 27 | const EspSoftApWiFiListState(this.wifiList); 28 | 29 | final List> wifiList; 30 | 31 | @override 32 | List get props => [wifiList]; 33 | } 34 | 35 | final class EspSoftApProvisioningInProgressState extends EspSoftApState { 36 | const EspSoftApProvisioningInProgressState({ 37 | required this.ssid, 38 | required this.password, 39 | }); 40 | 41 | final String ssid; 42 | final String password; 43 | 44 | @override 45 | List get props => [ssid, password]; 46 | } 47 | 48 | final class EspSoftApProvisioningDoneState extends EspSoftApState { 49 | const EspSoftApProvisioningDoneState(); 50 | } 51 | -------------------------------------------------------------------------------- /lib/modules/device/provisioning/soft_ap/di/esp_softap_di.dart: -------------------------------------------------------------------------------- 1 | import 'package:thingsboard_app/locator.dart'; 2 | import 'package:thingsboard_app/utils/services/provisioning/soft_ap/i_soft_ap_service.dart'; 3 | import 'package:thingsboard_app/utils/services/provisioning/soft_ap/soft_ap_service.dart'; 4 | 5 | abstract final class EspSoftApDi { 6 | static void init(String scopeName) { 7 | getIt.pushNewScope( 8 | scopeName: scopeName, 9 | init: (locator) { 10 | locator.registerFactory(() => SoftApService()); 11 | }, 12 | ); 13 | } 14 | 15 | static void dispose(String scopeName) { 16 | getIt.dropScope(scopeName); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /lib/modules/device/provisioning/view/device_provisioning_done.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter_gen/gen_l10n/messages.dart'; 3 | import 'package:flutter_svg/flutter_svg.dart'; 4 | import 'package:thingsboard_app/constants/assets_path.dart'; 5 | import 'package:thingsboard_app/modules/device/provisioning/widgets/return_to_dashboard_button.dart'; 6 | import 'package:thingsboard_app/utils/ui/tb_text_styles.dart'; 7 | 8 | class DeviceProvisioningDone extends StatelessWidget { 9 | const DeviceProvisioningDone({super.key}); 10 | 11 | @override 12 | Widget build(BuildContext context) { 13 | return Column( 14 | children: [ 15 | Center( 16 | child: SvgPicture.asset( 17 | ThingsboardImage.deviceProvisioningDone, 18 | width: 140, 19 | height: 140, 20 | ), 21 | ), 22 | const SizedBox(height: 16), 23 | Expanded( 24 | child: Center( 25 | child: Text( 26 | S.of(context).claimingMessageSuccess, 27 | textAlign: TextAlign.center, 28 | style: TbTextStyles.titleSmallSb.copyWith( 29 | color: Colors.black.withOpacity(.54), 30 | ), 31 | ), 32 | ), 33 | ), 34 | const Spacer(), 35 | ReturnToDashboardButton( 36 | onTap: () => Navigator.of(context).pop(true), 37 | ), 38 | ], 39 | ); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /lib/modules/device/provisioning/view/states/claiming_error.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter_gen/gen_l10n/messages.dart'; 3 | import 'package:thingsboard_app/modules/device/provisioning/view/states/connection_state_row.dart'; 4 | 5 | class ClaimingError extends StatelessWidget { 6 | const ClaimingError({super.key}); 7 | 8 | @override 9 | Widget build(BuildContext context) { 10 | return Column( 11 | children: [ 12 | ConnectionStateRow( 13 | S.of(context).sendingWifiCredentials, 14 | inProgress: false, 15 | ), 16 | ConnectionStateRow( 17 | S.of(context).confirmingWifiConnection, 18 | inProgress: false, 19 | ), 20 | ConnectionStateRow( 21 | S.of(context).provisionedSuccessfully, 22 | inProgress: false, 23 | ), 24 | ConnectionStateRow( 25 | S.of(context).claimingDevice, 26 | inProgress: false, 27 | error: true, 28 | ), 29 | ], 30 | ); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /lib/modules/device/provisioning/view/states/claiming_wip.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter_gen/gen_l10n/messages.dart'; 3 | import 'package:thingsboard_app/modules/device/provisioning/view/states/connection_state_row.dart'; 4 | 5 | class ClaimingWip extends StatelessWidget { 6 | const ClaimingWip({super.key}); 7 | 8 | @override 9 | Widget build(BuildContext context) { 10 | return Column( 11 | children: [ 12 | ConnectionStateRow( 13 | S.of(context).sendingWifiCredentials, 14 | inProgress: false, 15 | ), 16 | ConnectionStateRow( 17 | S.of(context).confirmingWifiConnection, 18 | inProgress: false, 19 | ), 20 | ConnectionStateRow( 21 | S.of(context).provisionedSuccessfully, 22 | inProgress: false, 23 | ), 24 | ConnectionStateRow( 25 | S.of(context).claimingDevice, 26 | inProgress: true, 27 | ), 28 | ], 29 | ); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /lib/modules/device/provisioning/view/states/confirming_wifi_connection.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter_gen/gen_l10n/messages.dart'; 3 | import 'package:thingsboard_app/modules/device/provisioning/view/states/connection_state_row.dart'; 4 | 5 | class ConfirmingWifiConnection extends StatelessWidget { 6 | const ConfirmingWifiConnection({super.key}); 7 | 8 | @override 9 | Widget build(BuildContext context) { 10 | return Column( 11 | children: [ 12 | ConnectionStateRow( 13 | S.of(context).sendingWifiCredentials, 14 | inProgress: false, 15 | ), 16 | ConnectionStateRow( 17 | S.of(context).confirmingWifiConnection, 18 | inProgress: true, 19 | ), 20 | ], 21 | ); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /lib/modules/device/provisioning/view/states/manually_reconnect_to_wifi.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:thingsboard_app/modules/device/provisioning/widgets/dotted_point_widget.dart'; 3 | import 'package:thingsboard_app/utils/ui/tb_text_styles.dart'; 4 | 5 | class ManuallyReconnectToWifi extends StatelessWidget { 6 | const ManuallyReconnectToWifi({super.key}); 7 | 8 | @override 9 | Widget build(BuildContext context) { 10 | return Column( 11 | children: [ 12 | Text( 13 | 'Please follow the next steps to reconnect ' 14 | 'your phone to your regular Wi-Fi', 15 | textAlign: TextAlign.center, 16 | style: TbTextStyles.bodyMedium.copyWith( 17 | color: Colors.black.withOpacity(.54), 18 | ), 19 | ), 20 | const SizedBox(height: 16), 21 | const DottedPointWidget('Open Wi-Fi settings'), 22 | const SizedBox(height: 16), 23 | const DottedPointWidget( 24 | 'Connect to the Wi-Fi you usually use', 25 | ), 26 | const SizedBox(height: 16), 27 | const DottedPointWidget( 28 | 'Return to the app and tap Ready button', 29 | ), 30 | ], 31 | ); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /lib/modules/device/provisioning/view/states/provision_error.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter_gen/gen_l10n/messages.dart'; 3 | import 'package:thingsboard_app/modules/device/provisioning/view/states/connection_state_row.dart'; 4 | 5 | class ProvisionError extends StatelessWidget { 6 | const ProvisionError({super.key}); 7 | 8 | @override 9 | Widget build(BuildContext context) { 10 | return Column( 11 | children: [ 12 | ConnectionStateRow( 13 | S.of(context).sendingWifiCredentials, 14 | inProgress: false, 15 | ), 16 | ConnectionStateRow( 17 | S.of(context).confirmingWifiConnection, 18 | inProgress: false, 19 | error: true, 20 | ), 21 | ], 22 | ); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /lib/modules/device/provisioning/view/states/provision_states.dart: -------------------------------------------------------------------------------- 1 | export 'claiming_error.dart'; 2 | export 'claiming_wip.dart'; 3 | export 'confirming_wifi_connection.dart'; 4 | export 'provision_error.dart'; 5 | export 'provision_success.dart'; 6 | export 'sending_wifi_credentials.dart'; 7 | -------------------------------------------------------------------------------- /lib/modules/device/provisioning/view/states/provision_success.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter_gen/gen_l10n/messages.dart'; 3 | import 'package:thingsboard_app/modules/device/provisioning/view/states/connection_state_row.dart'; 4 | 5 | class ProvisionSuccess extends StatelessWidget { 6 | const ProvisionSuccess({super.key}); 7 | 8 | @override 9 | Widget build(BuildContext context) { 10 | return Column( 11 | children: [ 12 | ConnectionStateRow( 13 | S.of(context).sendingWifiCredentials, 14 | inProgress: false, 15 | ), 16 | ConnectionStateRow( 17 | S.of(context).confirmingWifiConnection, 18 | inProgress: false, 19 | ), 20 | ConnectionStateRow( 21 | S.of(context).provisionedSuccessfully, 22 | inProgress: false, 23 | ), 24 | ], 25 | ); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /lib/modules/device/provisioning/view/states/sending_wifi_credentials.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter_gen/gen_l10n/messages.dart'; 3 | import 'package:thingsboard_app/modules/device/provisioning/view/states/connection_state_row.dart'; 4 | 5 | class SendingWifiCredentials extends StatelessWidget { 6 | const SendingWifiCredentials({super.key}); 7 | 8 | @override 9 | Widget build(BuildContext context) { 10 | return ConnectionStateRow( 11 | S.of(context).sendingWifiCredentials, 12 | inProgress: true, 13 | ); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /lib/modules/device/provisioning/widgets/dotted_point_widget.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:thingsboard_app/utils/ui/tb_text_styles.dart'; 3 | 4 | class DottedPointWidget extends StatelessWidget { 5 | const DottedPointWidget(this.text, {super.key}); 6 | 7 | final String text; 8 | 9 | @override 10 | Widget build(BuildContext context) { 11 | return Row( 12 | crossAxisAlignment: CrossAxisAlignment.center, 13 | children: [ 14 | Container( 15 | padding: const EdgeInsets.all(3), 16 | alignment: Alignment.center, 17 | decoration: BoxDecoration( 18 | color: Colors.black, 19 | shape: BoxShape.circle, 20 | border: Border.all( 21 | color: Colors.black.withOpacity(.54), 22 | ), 23 | ), 24 | ), 25 | const SizedBox(width: 16), 26 | Padding( 27 | padding: const EdgeInsets.only(bottom: 3), 28 | child: Text( 29 | text, 30 | style: TbTextStyles.bodyMedium.copyWith( 31 | color: Colors.black.withOpacity(.54), 32 | ), 33 | ), 34 | ), 35 | ], 36 | ); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /lib/modules/device/provisioning/widgets/exit_confirmation_dialog.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter_gen/gen_l10n/messages.dart'; 3 | import 'package:thingsboard_app/utils/ui/tb_alert_dialog.dart'; 4 | import 'package:thingsboard_app/utils/ui/tb_text_styles.dart'; 5 | 6 | class ExitConfirmationDialog extends StatelessWidget { 7 | const ExitConfirmationDialog({super.key}); 8 | 9 | @override 10 | Widget build(BuildContext context) { 11 | return TbAlertDialog( 12 | title: const Text('Exit device provisioning'), 13 | content: Text(S.of(context).areYouSure), 14 | actions: [ 15 | TextButton( 16 | onPressed: () => Navigator.of(context).pop(), 17 | child: Text( 18 | S.of(context).cancel.toUpperCase(), 19 | style: TbTextStyles.labelLarge.copyWith( 20 | color: Theme.of(context).primaryColor, 21 | ), 22 | ), 23 | ), 24 | TextButton( 25 | onPressed: () { 26 | Navigator.of(context).pop(); 27 | Navigator.of(context).pop(); 28 | }, 29 | child: Text( 30 | S.of(context).yes.toUpperCase(), 31 | style: TbTextStyles.labelLarge.copyWith( 32 | color: Colors.black.withOpacity(.87), 33 | ), 34 | ), 35 | ), 36 | ], 37 | ); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /lib/modules/device/provisioning/widgets/help_message_widget.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:thingsboard_app/utils/ui/tb_text_styles.dart'; 3 | 4 | class HelpTextWidget extends StatelessWidget { 5 | const HelpTextWidget(this.message, {super.key}); 6 | 7 | final String message; 8 | 9 | @override 10 | Widget build(BuildContext context) { 11 | return Container( 12 | width: double.infinity, 13 | padding: const EdgeInsets.symmetric( 14 | vertical: 12, 15 | horizontal: 16, 16 | ), 17 | decoration: BoxDecoration( 18 | color: Theme.of(context).primaryColor.withOpacity(.04), 19 | borderRadius: BorderRadius.circular(6), 20 | ), 21 | child: Text( 22 | message, 23 | textAlign: TextAlign.center, 24 | style: TbTextStyles.bodyMedium.copyWith( 25 | color: Colors.black.withOpacity(0.54), 26 | ), 27 | ), 28 | ); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /lib/modules/device/provisioning/widgets/return_to_dashboard_button.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter_gen/gen_l10n/messages.dart'; 3 | import 'package:thingsboard_app/utils/ui/tb_text_styles.dart'; 4 | 5 | class ReturnToDashboardButton extends StatelessWidget { 6 | const ReturnToDashboardButton({required this.onTap, super.key}); 7 | 8 | final VoidCallback onTap; 9 | 10 | @override 11 | Widget build(BuildContext context) { 12 | return SizedBox( 13 | width: double.infinity, 14 | child: ElevatedButton( 15 | style: ElevatedButton.styleFrom( 16 | padding: const EdgeInsets.symmetric( 17 | vertical: 12, 18 | horizontal: 16, 19 | ), 20 | ), 21 | onPressed: onTap, 22 | child: Text( 23 | S.of(context).returnToDashboard, 24 | style: TbTextStyles.labelMedium.copyWith(color: Colors.white), 25 | ), 26 | ), 27 | ); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /lib/modules/device/provisioning/widgets/scan_list_widget.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:thingsboard_app/utils/ui/tb_text_styles.dart'; 3 | 4 | class ScanListWidget extends StatelessWidget { 5 | const ScanListWidget( 6 | this.items, { 7 | required this.icon, 8 | this.onTap, 9 | super.key, 10 | }); 11 | 12 | final List items; 13 | final IconData icon; 14 | final Function(String item)? onTap; 15 | 16 | @override 17 | Widget build(BuildContext context) { 18 | return ListView.separated( 19 | itemBuilder: (context, index) { 20 | return InkWell( 21 | onTap: () => onTap?.call(items[index]), 22 | child: Padding( 23 | padding: const EdgeInsets.symmetric(vertical: 16), 24 | child: Row( 25 | children: [ 26 | Icon(icon, size: 24), 27 | const SizedBox(width: 16), 28 | Flexible( 29 | child: Text( 30 | items[index], 31 | style: TbTextStyles.bodyLarge.copyWith( 32 | color: Colors.black.withOpacity(.76), 33 | ), 34 | ), 35 | ), 36 | ], 37 | ), 38 | ), 39 | ); 40 | }, 41 | itemCount: items.length, 42 | separatorBuilder: (_, __) => 43 | const Divider(height: 1, thickness: .05, color: Colors.black), 44 | ); 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /lib/modules/device/provisioning/widgets/try_again_button.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter_gen/gen_l10n/messages.dart'; 3 | import 'package:thingsboard_app/utils/ui/tb_text_styles.dart'; 4 | 5 | class TryAgainButton extends StatelessWidget { 6 | const TryAgainButton({ 7 | required this.onTryAgain, 8 | this.label, 9 | super.key, 10 | }); 11 | 12 | final String? label; 13 | final VoidCallback onTryAgain; 14 | 15 | @override 16 | Widget build(BuildContext context) { 17 | return InkWell( 18 | onTap: onTryAgain, 19 | child: Container( 20 | width: double.infinity, 21 | padding: const EdgeInsets.symmetric( 22 | vertical: 12, 23 | horizontal: 16, 24 | ), 25 | decoration: BoxDecoration( 26 | border: Border.all( 27 | color: Colors.black.withOpacity(.12), 28 | width: 1, 29 | ), 30 | borderRadius: BorderRadius.circular(4), 31 | ), 32 | alignment: Alignment.center, 33 | child: Text( 34 | label ?? S.of(context).tryAgain, 35 | style: TbTextStyles.labelMedium.copyWith( 36 | color: Theme.of(context).primaryColor, 37 | ), 38 | ), 39 | ), 40 | ); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /lib/modules/home/home_routes.dart: -------------------------------------------------------------------------------- 1 | import 'package:fluro/fluro.dart'; 2 | import 'package:flutter/widgets.dart'; 3 | import 'package:thingsboard_app/config/routes/router.dart'; 4 | import 'package:thingsboard_app/core/context/tb_context.dart'; 5 | import 'package:thingsboard_app/modules/home/home_page.dart'; 6 | 7 | class HomeRoutes extends TbRoutes { 8 | late var homeHandler = Handler( 9 | handlerFunc: (BuildContext? context, Map params) { 10 | return HomePage(tbContext); 11 | }, 12 | ); 13 | 14 | HomeRoutes(TbContext tbContext) : super(tbContext); 15 | 16 | @override 17 | void doRegisterRoutes(router) { 18 | router.define('/home', handler: homeHandler); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /lib/modules/layout_pages/bloc/bloc.dart: -------------------------------------------------------------------------------- 1 | export 'layout_pages_bloc.dart'; 2 | export 'layout_pages_event.dart'; 3 | export 'layout_pages_state.dart'; 4 | -------------------------------------------------------------------------------- /lib/modules/layout_pages/bloc/layout_pages_event.dart: -------------------------------------------------------------------------------- 1 | import 'package:equatable/equatable.dart'; 2 | import 'package:flutter/cupertino.dart'; 3 | 4 | sealed class LayoutPagesEvent extends Equatable { 5 | const LayoutPagesEvent(); 6 | 7 | @override 8 | List get props => []; 9 | } 10 | 11 | final class BottomBarFetchEvent extends LayoutPagesEvent { 12 | const BottomBarFetchEvent(this.context); 13 | 14 | final BuildContext context; 15 | } 16 | 17 | final class BottomBarOrientationChangedEvent extends LayoutPagesEvent { 18 | const BottomBarOrientationChangedEvent(this.orientation, this.screenSize); 19 | 20 | final Orientation orientation; 21 | final Size screenSize; 22 | } 23 | -------------------------------------------------------------------------------- /lib/modules/layout_pages/bloc/layout_pages_state.dart: -------------------------------------------------------------------------------- 1 | import 'package:equatable/equatable.dart'; 2 | import 'package:thingsboard_app/modules/main/main_navigation_item.dart'; 3 | 4 | sealed class LayoutPagesState extends Equatable { 5 | const LayoutPagesState(); 6 | 7 | @override 8 | List get props => []; 9 | } 10 | 11 | final class BottomBarLoadingState extends LayoutPagesState { 12 | const BottomBarLoadingState(); 13 | } 14 | 15 | final class BottomBarDataState extends LayoutPagesState { 16 | const BottomBarDataState({required this.items}); 17 | 18 | final List items; 19 | 20 | @override 21 | List get props => [items]; 22 | } 23 | -------------------------------------------------------------------------------- /lib/modules/main/main_item_widget.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:thingsboard_app/core/context/tb_context_widget.dart'; 3 | 4 | class MainItemWidget extends TbContextWidget { 5 | MainItemWidget( 6 | super.tbContext, { 7 | required this.child, 8 | required this.path, 9 | super.key, 10 | }); 11 | 12 | final Widget? child; 13 | final String path; 14 | 15 | @override 16 | State createState() => _MainItemWidgetState(); 17 | } 18 | 19 | class _MainItemWidgetState extends TbContextState 20 | with AutomaticKeepAliveClientMixin { 21 | @override 22 | Widget build(BuildContext context) { 23 | super.build(context); 24 | return widget.child ?? 25 | Scaffold( 26 | appBar: AppBar(title: const Text('Not Found')), 27 | body: Center( 28 | child: Text('Route not defined: ${widget.path}'), 29 | ), 30 | ); 31 | } 32 | 33 | @override 34 | bool get wantKeepAlive => true; 35 | } 36 | -------------------------------------------------------------------------------- /lib/modules/main/main_navigation_item.dart: -------------------------------------------------------------------------------- 1 | import 'package:equatable/equatable.dart'; 2 | import 'package:flutter/material.dart'; 3 | 4 | class TbMainNavigationItem extends Equatable { 5 | const TbMainNavigationItem({ 6 | required this.page, 7 | required this.title, 8 | required this.icon, 9 | required this.path, 10 | this.id, 11 | this.showAdditionalIcon = false, 12 | this.additionalIconSmall, 13 | this.additionalIconLarge, 14 | }); 15 | 16 | final Widget page; 17 | final String title; 18 | final IconData icon; 19 | final String path; 20 | final String? id; 21 | final bool showAdditionalIcon; 22 | final Widget? additionalIconSmall; 23 | final Widget? additionalIconLarge; 24 | 25 | @override 26 | List get props => [ 27 | page, 28 | title, 29 | icon, 30 | path, 31 | id, 32 | showAdditionalIcon, 33 | additionalIconSmall, 34 | additionalIconLarge, 35 | ]; 36 | } 37 | -------------------------------------------------------------------------------- /lib/modules/main/main_routes.dart: -------------------------------------------------------------------------------- 1 | import 'package:fluro/fluro.dart'; 2 | import 'package:thingsboard_app/config/routes/router.dart'; 3 | import 'package:thingsboard_app/core/context/tb_context.dart'; 4 | import 'package:thingsboard_app/modules/main/main_page.dart'; 5 | 6 | class MainRoutes extends TbRoutes { 7 | MainRoutes(TbContext tbContext) : super(tbContext); 8 | 9 | @override 10 | void doRegisterRoutes(router) { 11 | router.define( 12 | '/main', 13 | handler: Handler( 14 | handlerFunc: (context, params) { 15 | return MainPage(tbContext); 16 | }, 17 | ), 18 | ); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /lib/modules/more/more_routes.dart: -------------------------------------------------------------------------------- 1 | import 'package:fluro/fluro.dart'; 2 | import 'package:thingsboard_app/config/routes/router.dart'; 3 | import 'package:thingsboard_app/core/context/tb_context.dart'; 4 | import 'package:thingsboard_app/modules/more/more_page.dart'; 5 | 6 | class MoreRoutes extends TbRoutes { 7 | late final moreHandler = Handler( 8 | handlerFunc: (context, params) { 9 | return MorePage(tbContext); 10 | }, 11 | ); 12 | 13 | MoreRoutes(TbContext tbContext) : super(tbContext); 14 | 15 | @override 16 | void doRegisterRoutes(router) { 17 | router.define('/more', handler: moreHandler); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /lib/modules/notification/controllers/notification_query_ctrl.dart: -------------------------------------------------------------------------------- 1 | import 'package:thingsboard_app/core/entity/entities_base.dart'; 2 | import 'package:thingsboard_app/thingsboard_client.dart'; 3 | 4 | class NotificationQueryCtrl extends PageKeyController { 5 | NotificationQueryCtrl({int pageSize = 20, String? searchText}) 6 | : super( 7 | PushNotificationQuery( 8 | TimePageLink( 9 | pageSize, 10 | 0, 11 | searchText, 12 | SortOrder('createdTime', Direction.DESC), 13 | ), 14 | unreadOnly: true, 15 | ), 16 | ); 17 | 18 | @override 19 | PushNotificationQuery nextPageKey(PushNotificationQuery pageKey) { 20 | return PushNotificationQuery( 21 | pageKey.pageLink.nextPageLink(), 22 | unreadOnly: value.pageKey.unreadOnly, 23 | ); 24 | } 25 | 26 | void onSearchText(String searchText) { 27 | final query = value.pageKey; 28 | query.pageLink.page = 0; 29 | query.pageLink.textSearch = searchText; 30 | 31 | notifyListeners(); 32 | } 33 | 34 | void filterByReadStatus(bool unreadOnly) { 35 | final query = value.pageKey; 36 | query.pageLink.page = 0; 37 | query.unreadOnly = unreadOnly; 38 | 39 | notifyListeners(); 40 | } 41 | 42 | void refresh() { 43 | notifyListeners(); 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /lib/modules/notification/repository/i_notification_query_repository.dart: -------------------------------------------------------------------------------- 1 | abstract interface class INotificationQueryRepository { 2 | Future markAllAsRead(); 3 | 4 | Future markNotificationAsRead(String id); 5 | 6 | Future deleteNotification(String id); 7 | 8 | Future searchNotification(String searchText); 9 | 10 | Future filterByReadStatus(bool unreadOnly); 11 | } 12 | -------------------------------------------------------------------------------- /lib/modules/notification/routes/notification_routes.dart: -------------------------------------------------------------------------------- 1 | import 'package:fluro/fluro.dart'; 2 | import 'package:thingsboard_app/config/routes/router.dart'; 3 | import 'package:thingsboard_app/core/context/tb_context.dart'; 4 | import 'package:thingsboard_app/modules/notification/notification_page.dart'; 5 | 6 | class NotificationRoutes extends TbRoutes { 7 | NotificationRoutes(TbContext tbContext) : super(tbContext); 8 | 9 | static const notificationRoutePath = '/notifications'; 10 | 11 | late final notificationHandler = Handler( 12 | handlerFunc: (context, params) { 13 | return NotificationPage(tbContext); 14 | }, 15 | ); 16 | 17 | @override 18 | void doRegisterRoutes(router) { 19 | router.define(notificationRoutePath, handler: notificationHandler); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /lib/modules/notification/service/i_notifications_local_service.dart: -------------------------------------------------------------------------------- 1 | abstract interface class INotificationsLocalService { 2 | Future increaseNotificationBadgeCount(); 3 | 4 | Future decreaseNotificationBadgeCount(); 5 | 6 | Future triggerNotificationCountStream(); 7 | 8 | Future clearNotificationBadgeCount(); 9 | 10 | Future updateNotificationsCount(int count); 11 | } 12 | -------------------------------------------------------------------------------- /lib/modules/notification/widgets/no_notifications_found_widget.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | class NoNotificationsFoundWidget extends StatelessWidget { 4 | const NoNotificationsFoundWidget({super.key}); 5 | 6 | @override 7 | Widget build(BuildContext context) { 8 | return const Center( 9 | child: Column( 10 | mainAxisAlignment: MainAxisAlignment.center, 11 | children: [ 12 | Text( 13 | 'No notifications found', 14 | style: TextStyle( 15 | fontWeight: FontWeight.w400, 16 | fontSize: 16, 17 | ), 18 | ), 19 | ], 20 | ), 21 | ); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /lib/modules/profile/profile_routes.dart: -------------------------------------------------------------------------------- 1 | import 'package:fluro/fluro.dart'; 2 | import 'package:flutter/widgets.dart'; 3 | import 'package:thingsboard_app/config/routes/router.dart'; 4 | import 'package:thingsboard_app/core/context/tb_context.dart'; 5 | 6 | import 'profile_page.dart'; 7 | 8 | class ProfileRoutes extends TbRoutes { 9 | late final profileHandler = Handler( 10 | handlerFunc: (BuildContext? context, Map params) { 11 | var fullscreen = params['fullscreen']?.first == 'true'; 12 | return ProfilePage(tbContext, fullscreen: fullscreen); 13 | }, 14 | ); 15 | 16 | ProfileRoutes(TbContext tbContext) : super(tbContext); 17 | 18 | @override 19 | void doRegisterRoutes(router) { 20 | router.define('/profile', handler: profileHandler); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /lib/modules/tenant/tenant_details_page.dart: -------------------------------------------------------------------------------- 1 | import 'package:thingsboard_app/core/context/tb_context.dart'; 2 | import 'package:thingsboard_app/core/entity/entity_details_page.dart'; 3 | import 'package:thingsboard_app/thingsboard_client.dart'; 4 | 5 | class TenantDetailsPage extends ContactBasedDetailsPage { 6 | TenantDetailsPage(TbContext tbContext, String tenantId, {super.key}) 7 | : super( 8 | tbContext, 9 | entityId: tenantId, 10 | defaultTitle: 'Tenant', 11 | subTitle: 'Tenant details', 12 | ); 13 | 14 | @override 15 | Future fetchEntity(String id) { 16 | return tbClient.getTenantService().getTenant(id); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /lib/modules/tenant/tenant_routes.dart: -------------------------------------------------------------------------------- 1 | import 'package:fluro/fluro.dart'; 2 | import 'package:flutter/widgets.dart'; 3 | import 'package:thingsboard_app/config/routes/router.dart'; 4 | import 'package:thingsboard_app/core/context/tb_context.dart'; 5 | 6 | import 'tenant_details_page.dart'; 7 | import 'tenants_page.dart'; 8 | 9 | class TenantRoutes extends TbRoutes { 10 | late var tenantsHandler = Handler( 11 | handlerFunc: (BuildContext? context, Map params) { 12 | var searchMode = params['search']?.first == 'true'; 13 | return TenantsPage(tbContext, searchMode: searchMode); 14 | }, 15 | ); 16 | 17 | late var tenantDetailsHandler = Handler( 18 | handlerFunc: (BuildContext? context, Map params) { 19 | return TenantDetailsPage(tbContext, params['id'][0]); 20 | }, 21 | ); 22 | 23 | TenantRoutes(TbContext tbContext) : super(tbContext); 24 | 25 | @override 26 | void doRegisterRoutes(router) { 27 | router.define('/tenants', handler: tenantsHandler); 28 | router.define('/tenant/:id', handler: tenantDetailsHandler); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /lib/modules/tenant/tenants_base.dart: -------------------------------------------------------------------------------- 1 | import 'package:thingsboard_app/core/entity/entities_base.dart'; 2 | import 'package:thingsboard_app/thingsboard_client.dart'; 3 | 4 | mixin TenantsBase on EntitiesBase { 5 | @override 6 | String get title => 'Tenants'; 7 | 8 | @override 9 | String get noItemsFoundText => 'No tenants found'; 10 | 11 | @override 12 | Future> fetchEntities(PageLink pageLink) { 13 | return tbClient.getTenantService().getTenants(pageLink); 14 | } 15 | 16 | @override 17 | void onEntityTap(Tenant tenant) { 18 | navigateTo('/tenant/${tenant.id!.id}'); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /lib/modules/tenant/tenants_list.dart: -------------------------------------------------------------------------------- 1 | import 'package:thingsboard_app/core/context/tb_context.dart'; 2 | import 'package:thingsboard_app/core/entity/entities_base.dart'; 3 | import 'package:thingsboard_app/core/entity/entities_list.dart'; 4 | import 'package:thingsboard_app/thingsboard_client.dart'; 5 | 6 | import 'tenants_base.dart'; 7 | 8 | class TenantsList extends BaseEntitiesWidget 9 | with TenantsBase, ContactBasedBase, EntitiesListStateBase { 10 | TenantsList( 11 | TbContext tbContext, 12 | PageKeyController pageKeyController, { 13 | searchMode = false, 14 | super.key, 15 | }) : super(tbContext, pageKeyController, searchMode: searchMode); 16 | } 17 | -------------------------------------------------------------------------------- /lib/modules/tenant/tenants_widget.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:thingsboard_app/core/context/tb_context.dart'; 3 | import 'package:thingsboard_app/core/context/tb_context_widget.dart'; 4 | import 'package:thingsboard_app/core/entity/entities_base.dart'; 5 | 6 | import 'tenants_list.dart'; 7 | 8 | class TenantsWidget extends TbContextWidget { 9 | TenantsWidget(TbContext tbContext, {super.key}) : super(tbContext); 10 | 11 | @override 12 | State createState() => _TenantsWidgetState(); 13 | } 14 | 15 | class _TenantsWidgetState extends TbContextState { 16 | final PageLinkController _pageLinkController = PageLinkController(); 17 | 18 | @override 19 | Widget build(BuildContext context) { 20 | return SafeArea( 21 | child: TenantsList(tbContext, _pageLinkController), 22 | ); 23 | } 24 | 25 | @override 26 | void dispose() { 27 | _pageLinkController.dispose(); 28 | super.dispose(); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /lib/modules/url/url_routes.dart: -------------------------------------------------------------------------------- 1 | import 'package:fluro/fluro.dart'; 2 | import 'package:thingsboard_app/config/routes/router.dart'; 3 | import 'package:thingsboard_app/core/context/tb_context.dart'; 4 | import 'package:thingsboard_app/modules/url/url_page.dart'; 5 | 6 | class UrlPageRoutes extends TbRoutes { 7 | UrlPageRoutes(TbContext tbContext) : super(tbContext); 8 | 9 | static const urlPageRoutes = '/url/:link'; 10 | 11 | late final urlPageHandler = Handler( 12 | handlerFunc: (context, params) { 13 | return UrlPage( 14 | url: Uri.decodeQueryComponent(params['link']?.first ?? ''), 15 | tbContext: tbContext, 16 | ); 17 | }, 18 | ); 19 | 20 | @override 21 | void doRegisterRoutes(router) { 22 | router.define(urlPageRoutes, handler: urlPageHandler); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /lib/modules/version/bloc/bloc.dart: -------------------------------------------------------------------------------- 1 | export 'version_info_bloc.dart'; 2 | export 'version_info_events.dart'; 3 | export 'version_info_states.dart'; 4 | -------------------------------------------------------------------------------- /lib/modules/version/bloc/version_info_events.dart: -------------------------------------------------------------------------------- 1 | import 'package:equatable/equatable.dart'; 2 | import 'package:thingsboard_app/modules/version/route/version_route_arguments.dart'; 3 | 4 | sealed class VersionInfoEvent extends Equatable { 5 | const VersionInfoEvent(); 6 | 7 | @override 8 | List get props => []; 9 | } 10 | 11 | final class VersionInfoFetchEvent extends VersionInfoEvent { 12 | const VersionInfoFetchEvent(this.args); 13 | 14 | final VersionRouteArguments args; 15 | 16 | @override 17 | List get props => [args]; 18 | } 19 | -------------------------------------------------------------------------------- /lib/modules/version/route/version_route.dart: -------------------------------------------------------------------------------- 1 | import 'package:fluro/fluro.dart'; 2 | import 'package:thingsboard_app/config/routes/router.dart'; 3 | import 'package:thingsboard_app/core/context/tb_context.dart'; 4 | import 'package:thingsboard_app/modules/version/route/version_route_arguments.dart'; 5 | import 'package:thingsboard_app/modules/version/view/update_required_page.dart'; 6 | 7 | class VersionRoutes extends TbRoutes { 8 | VersionRoutes(TbContext tbContext) : super(tbContext); 9 | 10 | static const updateRequiredRoutePath = '/updateRequired'; 11 | 12 | late final updateRequiredHandler = Handler( 13 | handlerFunc: (context, params) { 14 | final args = context?.settings?.arguments as VersionRouteArguments; 15 | return UpdateRequiredPage(tbContext, arguments: args); 16 | }, 17 | ); 18 | 19 | @override 20 | void doRegisterRoutes(router) { 21 | router.define(updateRequiredRoutePath, handler: updateRequiredHandler); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /lib/modules/version/route/version_route_arguments.dart: -------------------------------------------------------------------------------- 1 | import 'package:thingsboard_app/thingsboard_client.dart' 2 | show VersionInfo, StoreInfo; 3 | 4 | class VersionRouteArguments { 5 | const VersionRouteArguments({ 6 | required this.versionInfo, 7 | required this.storeInfo, 8 | }); 9 | 10 | final VersionInfo versionInfo; 11 | final StoreInfo? storeInfo; 12 | } 13 | -------------------------------------------------------------------------------- /lib/thingsboard_client.dart: -------------------------------------------------------------------------------- 1 | /// Since the CE and PE versions are mergeable, we frequently encounter merge 2 | /// conflicts due to the different names of the Dart client. 3 | /// The purpose of this file is to resolve these conflicts. 4 | /// 5 | /// By exporting the TB Client here, we ensure a consistent name for the client 6 | /// throughout the project. This file will change rarely, 7 | /// thus minimizing merge conflicts. 8 | 9 | export 'package:thingsboard_client/thingsboard_client.dart'; 10 | -------------------------------------------------------------------------------- /lib/utils/services/_tb_app_storage.dart: -------------------------------------------------------------------------------- 1 | import 'package:thingsboard_app/thingsboard_client.dart'; 2 | 3 | TbStorage createAppStorage() => throw UnsupportedError(''); 4 | -------------------------------------------------------------------------------- /lib/utils/services/_tb_web_local_storage.dart: -------------------------------------------------------------------------------- 1 | import 'package:thingsboard_app/thingsboard_client.dart'; 2 | 3 | TbStorage createAppStorage() => throw UnimplementedError(); 4 | -------------------------------------------------------------------------------- /lib/utils/services/communication/communication_service.dart: -------------------------------------------------------------------------------- 1 | import 'package:event_bus/event_bus.dart'; 2 | import 'package:thingsboard_app/utils/services/communication/events.dart'; 3 | import 'package:thingsboard_app/utils/services/communication/i_communication_service.dart'; 4 | 5 | class CommunicationService implements ICommunicationService { 6 | const CommunicationService(EventBus eventBus) : _eventBus = eventBus; 7 | 8 | final EventBus _eventBus; 9 | 10 | @override 11 | void fire(CommunicationEvent event) { 12 | _eventBus.fire(event); 13 | } 14 | 15 | @override 16 | Stream on() { 17 | return _eventBus.on(); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /lib/utils/services/communication/events.dart: -------------------------------------------------------------------------------- 1 | import 'package:equatable/equatable.dart'; 2 | import 'package:flutter/material.dart' show AppLifecycleState; 3 | import 'package:thingsboard_app/modules/device/provisioning/bloc/bloc.dart' 4 | show DeviceProvisioningStatus; 5 | import 'package:thingsboard_app/thingsboard_client.dart' show User; 6 | 7 | base class CommunicationEvent extends Equatable { 8 | const CommunicationEvent(); 9 | 10 | @override 11 | List get props => []; 12 | } 13 | 14 | final class UserLoggedInEvent extends CommunicationEvent { 15 | const UserLoggedInEvent(this.user); 16 | 17 | final User? user; 18 | 19 | @override 20 | List get props => [user]; 21 | } 22 | 23 | final class AlarmAssigneeUpdatedEvent extends CommunicationEvent { 24 | const AlarmAssigneeUpdatedEvent(this.id); 25 | 26 | final String? id; 27 | 28 | @override 29 | List get props => []; 30 | } 31 | 32 | final class DeviceProvisioningStatusChangedEvent extends CommunicationEvent { 33 | const DeviceProvisioningStatusChangedEvent(this.status); 34 | 35 | final DeviceProvisioningStatus status; 36 | 37 | @override 38 | List get props => [double.nan]; 39 | } 40 | 41 | final class AppLifecycleStateChangedEvent extends CommunicationEvent { 42 | const AppLifecycleStateChangedEvent(this.state); 43 | 44 | final AppLifecycleState state; 45 | 46 | @override 47 | List get props => [state]; 48 | } 49 | -------------------------------------------------------------------------------- /lib/utils/services/communication/i_communication_service.dart: -------------------------------------------------------------------------------- 1 | import 'package:thingsboard_app/utils/services/communication/events.dart'; 2 | 3 | abstract interface class ICommunicationService { 4 | Stream on(); 5 | 6 | void fire(CommunicationEvent event); 7 | } 8 | -------------------------------------------------------------------------------- /lib/utils/services/device_profile_cache.dart: -------------------------------------------------------------------------------- 1 | import 'package:thingsboard_app/thingsboard_client.dart'; 2 | 3 | abstract class DeviceProfileCache { 4 | static final _cache = {}; 5 | 6 | static Future getDeviceProfileInfo( 7 | ThingsboardClient tbClient, 8 | String name, 9 | String deviceId, 10 | ) async { 11 | var deviceProfile = _cache[name]; 12 | if (deviceProfile == null) { 13 | var device = await tbClient.getDeviceService().getDevice(deviceId); 14 | deviceProfile = await tbClient 15 | .getDeviceProfileService() 16 | .getDeviceProfileInfo(device!.deviceProfileId!.id!); 17 | _cache[name] = deviceProfile!; 18 | } 19 | return deviceProfile; 20 | } 21 | 22 | static Future> getDeviceProfileInfos( 23 | ThingsboardClient tbClient, 24 | PageLink pageLink, 25 | ) async { 26 | final deviceProfileInfos = await tbClient 27 | .getDeviceProfileService() 28 | .getDeviceProfileInfos(pageLink); 29 | 30 | for (final deviceProfile in deviceProfileInfos.data) { 31 | _cache[deviceProfile.name] = deviceProfile; 32 | } 33 | 34 | return deviceProfileInfos; 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /lib/utils/services/endpoint/i_endpoint_service.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/foundation.dart'; 2 | import 'package:thingsboard_app/core/auth/login/region.dart'; 3 | 4 | /// This service provides information about the current active endpoint. 5 | /// Since we have a feature that allows for changing endpoints, there is some 6 | /// logic associated with the active endpoint, such as dashboard loading and OAuth2A. 7 | abstract interface class IEndpointService { 8 | Future setEndpoint(String endpoint); 9 | 10 | Future getEndpoint(); 11 | 12 | Future isCustomEndpoint(); 13 | 14 | /// At times, we need to retrieve the endpoint synchronously. 15 | /// We might consider using Hive in the future. 16 | String getCachedEndpoint(); 17 | 18 | Future setRegion(Region region); 19 | 20 | Future getSelectedRegion(); 21 | 22 | ValueListenable get listenEndpointChanges; 23 | } 24 | -------------------------------------------------------------------------------- /lib/utils/services/firebase/i_firebase_service.dart: -------------------------------------------------------------------------------- 1 | import 'package:firebase_core/firebase_core.dart'; 2 | 3 | abstract interface class IFirebaseService { 4 | const IFirebaseService(); 5 | 6 | Future initializeApp({String name, FirebaseOptions? options}); 7 | 8 | Future removeApp({String name}); 9 | 10 | List get apps; 11 | } 12 | -------------------------------------------------------------------------------- /lib/utils/services/layouts/i_layout_service.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/cupertino.dart'; 2 | import 'package:thingsboard_app/core/context/tb_context.dart'; 3 | import 'package:thingsboard_app/modules/main/main_navigation_item.dart'; 4 | import 'package:thingsboard_app/thingsboard_client.dart'; 5 | 6 | abstract interface class ILayoutService { 7 | List getBottomBarItems(); 8 | 9 | void setDeviceScreenSize(Size size, {required Orientation orientation}); 10 | 11 | void setBottomBarItems( 12 | List items, { 13 | required TbMainNavigationItem more, 14 | }); 15 | 16 | List getMorePageItems( 17 | TbContext tbContext, 18 | BuildContext context, 19 | ); 20 | 21 | void cachePageLayouts( 22 | List? pages, { 23 | required Authority authority, 24 | }); 25 | 26 | List getCachedPageLayouts(); 27 | } 28 | -------------------------------------------------------------------------------- /lib/utils/services/local_database/i_local_database_service.dart: -------------------------------------------------------------------------------- 1 | import 'package:thingsboard_app/core/auth/login/region.dart'; 2 | 3 | /// The aim of this service is to consolidate operations with 4 | /// the local database provider into one centralized location. 5 | 6 | abstract interface class ILocalDatabaseService { 7 | Future getSelectedRegion(); 8 | 9 | Future saveSelectedRegion(Region region); 10 | 11 | Future getSelectedEndpoint(); 12 | 13 | Future setSelectedEndpoint(String endpoint); 14 | 15 | Future setInitialAppLink(String appLink); 16 | 17 | Future getInitialAppLink(); 18 | 19 | Future deleteInitialAppLink(); 20 | } 21 | -------------------------------------------------------------------------------- /lib/utils/services/provisioning/eps_ble/i_wifi_provisioning_service.dart: -------------------------------------------------------------------------------- 1 | abstract interface class IBleProvisioningService { 2 | Future> scanBleDevices(String prefix); 3 | 4 | Future> scanWifiNetworks({ 5 | required String deviceName, 6 | required String proofOfPossession, 7 | }); 8 | 9 | Future provisionWifi({ 10 | required String deviceName, 11 | required String proofOfPossession, 12 | required String ssid, 13 | required String passphrase, 14 | }); 15 | 16 | Future getPlatformVersion(); 17 | } 18 | -------------------------------------------------------------------------------- /lib/utils/services/provisioning/eps_ble/wifi_provisioning_service.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter_esp_ble_prov/flutter_esp_ble_prov.dart'; 2 | import 'package:thingsboard_app/utils/services/provisioning/eps_ble/i_wifi_provisioning_service.dart'; 3 | 4 | class BleProvisioningService implements IBleProvisioningService { 5 | BleProvisioningService([FlutterEspBleProv? prov]) 6 | : espBleProv = prov ?? FlutterEspBleProv(); 7 | 8 | final FlutterEspBleProv espBleProv; 9 | 10 | @override 11 | Future provisionWifi({ 12 | required String deviceName, 13 | required String proofOfPossession, 14 | required String ssid, 15 | required String passphrase, 16 | }) async { 17 | return espBleProv.provisionWifi( 18 | deviceName, 19 | proofOfPossession, 20 | ssid, 21 | passphrase, 22 | ); 23 | } 24 | 25 | @override 26 | Future> scanBleDevices(String prefix) async { 27 | return espBleProv.scanBleDevices(prefix); 28 | } 29 | 30 | @override 31 | Future> scanWifiNetworks({ 32 | required String deviceName, 33 | required String proofOfPossession, 34 | }) async { 35 | return espBleProv.scanWifiNetworks(deviceName, proofOfPossession); 36 | } 37 | 38 | @override 39 | Future getPlatformVersion() { 40 | return espBleProv.getPlatformVersion(); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /lib/utils/services/provisioning/esp_smartconfig/esp_smartconfig_service.dart: -------------------------------------------------------------------------------- 1 | // // import 'package:esp_smartconfig/esp_smartconfig.dart'; 2 | // import 'package:thingsboard_app/utils/services/provisioning/esp_smartconfig/i_esp_smartconfig_service.dart'; 3 | // 4 | // class EspSmartConfigService implements IEspSmartConfigService { 5 | // const EspSmartConfigService(this.provisioner); 6 | // 7 | // // final Provisioner provisioner; 8 | // 9 | // @override 10 | // Provisioner create(SmartConfig config) { 11 | // switch (config) { 12 | // case SmartConfig.espTouch: 13 | // return Provisioner.espTouch(); 14 | // case SmartConfig.espTouchV2: 15 | // return Provisioner.espTouchV2(); 16 | // } 17 | // } 18 | // 19 | // @override 20 | // Future start(ProvisioningRequest request) async { 21 | // return provisioner.start(request); 22 | // } 23 | // 24 | // @override 25 | // Future stop() async { 26 | // provisioner.stop(); 27 | // } 28 | // } 29 | -------------------------------------------------------------------------------- /lib/utils/services/provisioning/esp_smartconfig/i_esp_smartconfig_service.dart: -------------------------------------------------------------------------------- 1 | // import 'package:esp_smartconfig/esp_smartconfig.dart'; 2 | // 3 | // enum SmartConfig { espTouch, espTouchV2 } 4 | // 5 | // abstract interface class IEspSmartConfigService { 6 | // Provisioner create(SmartConfig config); 7 | // 8 | // Future start(ProvisioningRequest request); 9 | // 10 | // Future stop(); 11 | // } 12 | -------------------------------------------------------------------------------- /lib/utils/services/provisioning/soft_ap/i_soft_ap_service.dart: -------------------------------------------------------------------------------- 1 | import 'dart:typed_data' show Uint8List; 2 | 3 | import 'package:esp_provisioning_softap/esp_provisioning_softap.dart'; 4 | 5 | abstract interface class ISoftApService { 6 | Future startProvisioning({ 7 | required String hostname, 8 | required String pop, 9 | }); 10 | 11 | Future>?> startScanWiFi(Provisioning prov); 12 | 13 | Future sendReceiveCustomData( 14 | Provisioning prov, { 15 | required Uint8List data, 16 | int packageSize = 256, 17 | String endpoint = 'custom-data', 18 | }); 19 | 20 | Future sendWifiConfig( 21 | Provisioning prov, { 22 | required String ssid, 23 | required String password, 24 | }); 25 | 26 | Future applyWifiConfig(Provisioning prov); 27 | 28 | Future getStatus(Provisioning prov); 29 | } 30 | -------------------------------------------------------------------------------- /lib/utils/services/tb_app_storage.dart: -------------------------------------------------------------------------------- 1 | export '_tb_app_storage.dart' 2 | if (dart.library.io) '_tb_secure_storage.dart' 3 | if (dart.library.html) '_tb_web_local_storage.dart'; 4 | -------------------------------------------------------------------------------- /lib/utils/services/user/i_user_service.dart: -------------------------------------------------------------------------------- 1 | abstract interface class IUserService {} 2 | -------------------------------------------------------------------------------- /lib/utils/services/user/user_service.dart: -------------------------------------------------------------------------------- 1 | import 'package:thingsboard_app/locator.dart'; 2 | import 'package:thingsboard_app/utils/services/communication/events.dart'; 3 | import 'package:thingsboard_app/utils/services/communication/i_communication_service.dart'; 4 | import 'package:thingsboard_app/utils/services/user/i_user_service.dart'; 5 | 6 | class UserService implements IUserService { 7 | UserService() { 8 | getIt().on().listen((user) {}); 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /lib/utils/string_utils.dart: -------------------------------------------------------------------------------- 1 | extension EmailValidator on String { 2 | bool isValidEmail() { 3 | return RegExp( 4 | r'^(([^<>()[\]\\.,;:\s@\"]+(\.[^<>()[\]\\.,;:\s@\"]+)*)|(\".+\"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$', 5 | ).hasMatch(this); 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /lib/utils/ui/back_button_widget.dart: -------------------------------------------------------------------------------- 1 | import 'dart:io'; 2 | 3 | import 'package:flutter/material.dart'; 4 | 5 | class BackButtonWidget extends StatelessWidget { 6 | const BackButtonWidget({this.onPressed, super.key}); 7 | 8 | final VoidCallback? onPressed; 9 | 10 | @override 11 | Widget build(BuildContext context) { 12 | return IconButton( 13 | onPressed: onPressed, 14 | icon: Icon( 15 | Platform.isIOS ? Icons.arrow_back_ios : Icons.arrow_back, 16 | ), 17 | ); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /lib/utils/ui/pagination_list_widget.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/cupertino.dart'; 2 | import 'package:flutter/material.dart'; 3 | import 'package:flutter/widgets.dart'; 4 | import 'package:infinite_scroll_pagination/infinite_scroll_pagination.dart'; 5 | 6 | class PaginationListWidget extends StatelessWidget { 7 | const PaginationListWidget({ 8 | required this.pagingController, 9 | required this.builderDelegate, 10 | this.separatorWidgetBuilder, 11 | this.heading, 12 | super.key, 13 | }); 14 | 15 | final Widget? heading; 16 | final PagingController pagingController; 17 | final PagedChildBuilderDelegate builderDelegate; 18 | final IndexedWidgetBuilder? separatorWidgetBuilder; 19 | 20 | @override 21 | Widget build(BuildContext context) { 22 | return CustomScrollView( 23 | slivers: [ 24 | SliverVisibility( 25 | visible: heading != null, 26 | sliver: SliverPadding( 27 | padding: const EdgeInsets.fromLTRB(16, 16, 16, 0), 28 | sliver: SliverToBoxAdapter(child: heading), 29 | ), 30 | ), 31 | SliverPadding( 32 | padding: const EdgeInsets.all(16), 33 | sliver: PagedSliverList.separated( 34 | pagingController: pagingController, 35 | builderDelegate: builderDelegate, 36 | separatorBuilder: 37 | separatorWidgetBuilder ?? (_, __) => const SizedBox(height: 16), 38 | ), 39 | ), 40 | ], 41 | ); 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /lib/utils/ui/pagination_widgets/first_page_progress_builder.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | class FirstPageProgressBuilder extends StatelessWidget { 4 | const FirstPageProgressBuilder({super.key}); 5 | 6 | @override 7 | Widget build(BuildContext context) { 8 | return const Stack( 9 | children: [ 10 | Positioned( 11 | top: 20, 12 | left: 0, 13 | right: 0, 14 | child: Row( 15 | mainAxisAlignment: MainAxisAlignment.center, 16 | children: [ 17 | RefreshProgressIndicator(), 18 | ], 19 | ), 20 | ), 21 | ], 22 | ); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /lib/utils/ui/pagination_widgets/new_page_progress_builder.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | class NewPageProgressBuilder extends StatelessWidget { 4 | const NewPageProgressBuilder({super.key}); 5 | 6 | @override 7 | Widget build(BuildContext context) { 8 | return const Padding( 9 | padding: EdgeInsets.only( 10 | top: 16, 11 | bottom: 16, 12 | ), 13 | child: Center( 14 | child: RefreshProgressIndicator(), 15 | ), 16 | ); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /lib/utils/ui/tb_alert_dialog.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:thingsboard_app/utils/ui/tb_text_styles.dart'; 3 | 4 | class TbAlertDialog extends StatelessWidget { 5 | const TbAlertDialog({ 6 | required this.title, 7 | required this.content, 8 | required this.actions, 9 | super.key, 10 | }); 11 | 12 | final Widget title; 13 | final Widget content; 14 | final List actions; 15 | 16 | @override 17 | Widget build(BuildContext context) { 18 | return AlertDialog( 19 | title: title, 20 | titleTextStyle: TbTextStyles.titleXs.copyWith( 21 | color: const Color(0xff1D1B20), 22 | ), 23 | content: content, 24 | contentTextStyle: TbTextStyles.bodyMedium.copyWith( 25 | color: const Color(0xff49454F), 26 | ), 27 | actions: actions, 28 | shape: const RoundedRectangleBorder( 29 | borderRadius: BorderRadius.all(Radius.circular(28)), 30 | ), 31 | ); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /lib/utils/ui/ui_utils.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:modal_bottom_sheet/modal_bottom_sheet.dart'; 3 | 4 | abstract class UiUtils { 5 | static Future showModalBottomSheet({ 6 | required BuildContext context, 7 | required WidgetBuilder builder, 8 | Color barrierColor = Colors.black54, 9 | Widget? topControl, 10 | }) async { 11 | return showBarModalBottomSheet( 12 | context: context, 13 | builder: builder, 14 | barrierColor: barrierColor, 15 | topControl: topControl, 16 | ); 17 | } 18 | 19 | static Color colorFromString(String str) { 20 | return HSLColor.fromAHSL( 21 | 1, 22 | str.hashCode % 360, 23 | 40 / 100, 24 | 60 / 100, 25 | ).toColor(); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /lib/utils/ui_utils_routes.dart: -------------------------------------------------------------------------------- 1 | import 'package:fluro/fluro.dart'; 2 | import 'package:flutter/widgets.dart'; 3 | import 'package:thingsboard_app/config/routes/router.dart'; 4 | import 'package:thingsboard_app/core/context/tb_context.dart'; 5 | import 'package:thingsboard_app/utils/ui/qr_code_scanner.dart'; 6 | 7 | class UiUtilsRoutes extends TbRoutes { 8 | late final qrCodeScannerHandler = Handler( 9 | handlerFunc: (BuildContext? context, Map params) { 10 | return QrCodeScannerPage(tbContext); 11 | }, 12 | ); 13 | 14 | UiUtilsRoutes(TbContext tbContext) : super(tbContext); 15 | 16 | @override 17 | void doRegisterRoutes(router) { 18 | router.define('/qrCodeScan', handler: qrCodeScannerHandler); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /lib/utils/usecase.dart: -------------------------------------------------------------------------------- 1 | abstract class UseCase { 2 | const UseCase(); 3 | 4 | Output call(Input params); 5 | } 6 | 7 | class NoParams { 8 | const NoParams(); 9 | } 10 | -------------------------------------------------------------------------------- /lib/widgets/two_value_listenable_builder.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/foundation.dart'; 2 | import 'package:flutter/widgets.dart'; 3 | 4 | class TwoValueListenableBuilder extends StatelessWidget { 5 | const TwoValueListenableBuilder({ 6 | required this.firstValueListenable, 7 | required this.secondValueListenable, 8 | required this.builder, 9 | this.child, 10 | super.key, 11 | }); 12 | 13 | final ValueListenable firstValueListenable; 14 | final ValueListenable secondValueListenable; 15 | final Widget? child; 16 | final Widget Function(BuildContext context, A a, B b, Widget? child) builder; 17 | 18 | @override 19 | Widget build(BuildContext context) { 20 | return ValueListenableBuilder( 21 | valueListenable: firstValueListenable, 22 | builder: (_, a, __) { 23 | return ValueListenableBuilder( 24 | valueListenable: secondValueListenable, 25 | builder: (context, b, __) { 26 | return builder(context, a, b, child); 27 | }, 28 | ); 29 | }, 30 | ); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /test/mocks.dart: -------------------------------------------------------------------------------- 1 | import 'package:mocktail/mocktail.dart'; 2 | import 'package:thingsboard_app/core/context/tb_context.dart'; 3 | import 'package:thingsboard_app/thingsboard_client.dart'; 4 | import 'package:thingsboard_app/utils/services/endpoint/endpoint_service.dart'; 5 | import 'package:thingsboard_app/utils/services/firebase/firebase_service.dart'; 6 | 7 | class MockTbContext extends Mock implements TbContext {} 8 | 9 | class MockTbClient extends Mock implements ThingsboardClient {} 10 | 11 | class MockFirebaseService extends Mock implements FirebaseService {} 12 | 13 | class MockEndpointService extends Mock implements EndpointService {} 14 | --------------------------------------------------------------------------------