Skip to content

Commit

Permalink
feat(digital-guide): create digital guide screen (#442)
Browse files Browse the repository at this point in the history
* chore: add temp button on main screen

* feat: add simple view and navigation

* feat: initially implement UI in building detail view

* feat: add building info section UI

* feat: add extenstion tiles, adjust building info section, lint rules

* fix: lint rules

* fix: review fixes

* feat: rename building_details_view package to digital_guide_view and add initial api for digital guide without authorization handling

* feat: add authorization for digital guide API (with token) and add loading animation

* feat: fetch building data and present general info in detail view

* fix: add variables for digital guide to example.env file

* feat: create reamdme md file about API

* feat: implement partially utilities and surrounding expansion tiles

* fix: minor improvements after reviews

* fix: improve readme.md for the API

* refractor: remove image repository and fetch imageUrl in digital guide repository

* chore: add TODO and reflection comments as follow-up to reviews

* refractor: skip method extraction in amenities expansion tile

* refractor: feature catalog structure

* refractor: combine imageUrl and digitalGuideResponse into on class and remove error handling from fetching imageUrl

* fix: add error handling while fetching image url for other data to display

* docs: update and improve readme.md for the feature

* fix: follow mock on Figma for accesibility button

* fix: reorganize assets svg

* refractor: replace SliverChildListDeletage with SliverChildBuilderDeletage

* refractor: replace Columns with slivers

* fix: lint rules and formatting

* fix: ensure type safety

* fix: post merge fix

---------

Co-authored-by: Szymon Kowaliński <[email protected]>
  • Loading branch information
24bartixx and simon-the-shark authored Dec 8, 2024
1 parent aa6c7d8 commit cbf1c77
Show file tree
Hide file tree
Showing 27 changed files with 852 additions and 11 deletions.
1 change: 1 addition & 0 deletions assets/svg/digital_guide/storey.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
4 changes: 3 additions & 1 deletion example.env
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,6 @@ ASSETS_URL="https://<...>"
IPARKING_URL="https://<...>"
WIREDASH_ID="<...>"
WIREDASH_SECRET="<...>"
SKS_URL="<...>"
SKS_URL="<...>"
DIGITAL_GUIDE_URL="<...>"
DIGITAL_GUIDE_AUTHORIZATION_TOKEN="<...>"
5 changes: 5 additions & 0 deletions lib/config/env.dart
Original file line number Diff line number Diff line change
Expand Up @@ -27,4 +27,9 @@ abstract class Env {
static final String wiredashSecret = _Env.wiredashSecret;
@EnviedField()
static final String sksUrl = _Env.sksUrl;
@EnviedField()
static final String digitalGuideUrl = _Env.digitalGuideUrl;
@EnviedField()
static final String digitalGuideAuthorizationToken =
_Env.digitalGuideAuthorizationToken;
}
15 changes: 15 additions & 0 deletions lib/config/ui_config.dart
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,12 @@ abstract class MyAppConfig {
"\u{a9} 2024 Koło Naukowe Solvro, Politechnika Wrocławska";
}

abstract class AppWidgetsConfig {
static const paddingMedium =
EdgeInsets.symmetric(horizontal: 16, vertical: 12);
static const borderRadiusMedium = 8.0;
}

abstract class SplashScreenConfig {
static const additionalWaitDuration = Duration(seconds: 1);
static const animationDuration = Duration(milliseconds: 800);
Expand Down Expand Up @@ -201,6 +207,15 @@ abstract class NavigationTabViewConfig {
static const navIconSize = 30.0;
}

abstract class DigitalGuideConfig {
static const symetricalPaddingBig =
EdgeInsets.symmetric(vertical: 24, horizontal: 24);
static const borderRadiusMedium = 8.0;
static const heightSmall = 8.0;
static const heightBig = 24.0;
static const heightHuge = 48.0;
}

abstract class AlertDialogConfig {
static const horizontalPadding = 14.0;
static const verticalPadding = 20.0;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
import "package:fast_immutable_collections/fast_immutable_collections.dart";
import "package:flutter/widgets.dart";

import "../../../../gen/assets.gen.dart";
import "../../../../utils/context_extensions.dart";
import "../../../../utils/determine_contact_icon.dart";
import "../../../../widgets/detail_views/contact_section.dart";
import "../../general_info/data/models/digital_guide_response_extended.dart";

class AmenitiesExpansionTileContent extends StatelessWidget {
const AmenitiesExpansionTileContent({
required this.digitalGuideResponseExtended,
});

final DigitalGuideResponseExtended digitalGuideResponseExtended;

@override
Widget build(BuildContext context) {
return ContactSection(
list: [
if (digitalGuideResponseExtended.canAssistanceDog)
ContactIconsModel(
text: context.localize.assistance_dog,
icon: Assets.svg.contactIcons.compass,
),
if (digitalGuideResponseExtended.isInductionLoop)
ContactIconsModel(
text: context.localize.induction_loop,
icon: Assets.svg.contactIcons.compass,
),
if (digitalGuideResponseExtended.isMicroNavigationSystem)
ContactIconsModel(
text: context.localize.micronavigation_system,
icon: Assets.svg.contactIcons.compass,
),
if (digitalGuideResponseExtended.areGuidancePaths)
ContactIconsModel(
text: context.localize.guidance_paths,
icon: Assets.svg.contactIcons.compass,
),
if (digitalGuideResponseExtended.areBrailleBoards)
ContactIconsModel(
text: context.localize.information_boards_with_braille_description,
icon: Assets.svg.contactIcons.compass,
),
if (digitalGuideResponseExtended.areLargeFontBoards)
ContactIconsModel(
text: context.localize.information_boards_with_large_font,
icon: Assets.svg.contactIcons.compass,
),
if (digitalGuideResponseExtended.isSignLanguageInterpreter)
ContactIconsModel(
text: context.localize.sign_language_interpreter,
icon: Assets.svg.contactIcons.compass,
),
if (digitalGuideResponseExtended.areEmergencyChairs)
ContactIconsModel(
text: context.localize.emergency_chairs,
icon: Assets.svg.contactIcons.compass,
),
].lock,
);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
// ignore_for_file: invalid_annotation_target

import "package:freezed_annotation/freezed_annotation.dart";

part "digital_guide_response.freezed.dart";
part "digital_guide_response.g.dart";

@freezed
class DigitalGuideResponse with _$DigitalGuideResponse {
const factory DigitalGuideResponse({
required int id,
required DigitalGuideTranslations translations,
@JsonKey(name: "number_of_storeys") required int numberOfStoreys,
@JsonKey(
name: "is_possibility_to_enter_with_assistance_dog",
fromJson: _stringToBool,
)
required bool canAssistanceDog,
@JsonKey(
name: "is_induction_loop",
fromJson: _stringToBool,
)
required bool isInductionLoop,
@JsonKey(
name: "is_micronavigation_system",
fromJson: _stringToBool,
)
required bool isMicroNavigationSystem,
@JsonKey(
name: "are_guidance_paths",
fromJson: _stringToBool,
)
required bool areGuidancePaths,
@JsonKey(
name: "are_information_boards_with_braille_description",
fromJson: _stringToBool,
)
required bool areBrailleBoards,
@JsonKey(
name: "are_information_boards_with_large_font",
fromJson: _stringToBool,
)
required bool areLargeFontBoards,
@JsonKey(
name: "is_sign_language_interpreter",
fromJson: _stringToBool,
)
required bool isSignLanguageInterpreter,
@JsonKey(
name: "are_emergency_chairs",
fromJson: _stringToBool,
)
required bool areEmergencyChairs,
@JsonKey(name: "telephone_number", fromJson: _formatTelephoneNumber)
required String telephoneNumber,
@JsonKey(name: "surrounding") required int surroundingId,
required List<int> images,
String? imageUrl,
}) = _DigitalGuideResponse;

factory DigitalGuideResponse.fromJson(Map<String, dynamic> json) =>
_$DigitalGuideResponseFromJson(json);
}

@freezed
class DigitalGuideTranslations with _$DigitalGuideTranslations {
const factory DigitalGuideTranslations({
@JsonKey(name: "pl") required DigitalGuideTranslation plTranslation,
}) = _DigitalGuideTranslations;

factory DigitalGuideTranslations.fromJson(Map<String, dynamic> json) =>
_$DigitalGuideTranslationsFromJson(json);
}

@freezed
class DigitalGuideTranslation with _$DigitalGuideTranslation {
const factory DigitalGuideTranslation({
required String name,
@JsonKey(name: "extended_name") required String extendedName,
required String address,
}) = _DigitalGuideTranslation;

factory DigitalGuideTranslation.fromJson(Map<String, dynamic> json) =>
_$DigitalGuideTranslationFromJson(json);
}

bool _stringToBool(String value) {
return value == "True";
}

String _formatTelephoneNumber(String telephoneNumber) {
return telephoneNumber.replaceAll("<p>", "").replaceAll("</p>", "");
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
import "dart:core";

import "digital_guide_response.dart";

class DigitalGuideResponseExtended {
const DigitalGuideResponseExtended({
required this.id,
required this.translations,
required this.numberOfStoreys,
required this.canAssistanceDog,
required this.isInductionLoop,
required this.isMicroNavigationSystem,
required this.areGuidancePaths,
required this.areBrailleBoards,
required this.areLargeFontBoards,
required this.isSignLanguageInterpreter,
required this.areEmergencyChairs,
required this.telephoneNumber,
required this.surroundingId,
required this.images,
required this.imageUrl,
});

final int id;
final DigitalGuideTranslations translations;
final int numberOfStoreys;
final bool canAssistanceDog;
final bool isInductionLoop;
final bool isMicroNavigationSystem;
final bool areGuidancePaths;
final bool areBrailleBoards;
final bool areLargeFontBoards;
final bool isSignLanguageInterpreter;
final bool areEmergencyChairs;
final String telephoneNumber;
final int surroundingId;
final List<int> images;
final String? imageUrl;

factory DigitalGuideResponseExtended.fromDigitalGuideResponse({
required DigitalGuideResponse digitalGuideResponse,
required String? imageUrl,
}) {
return DigitalGuideResponseExtended(
id: digitalGuideResponse.id,
translations: digitalGuideResponse.translations,
numberOfStoreys: digitalGuideResponse.numberOfStoreys,
canAssistanceDog: digitalGuideResponse.canAssistanceDog,
isInductionLoop: digitalGuideResponse.isInductionLoop,
isMicroNavigationSystem: digitalGuideResponse.isMicroNavigationSystem,
areGuidancePaths: digitalGuideResponse.areGuidancePaths,
areBrailleBoards: digitalGuideResponse.areBrailleBoards,
areLargeFontBoards: digitalGuideResponse.areLargeFontBoards,
isSignLanguageInterpreter: digitalGuideResponse.isSignLanguageInterpreter,
areEmergencyChairs: digitalGuideResponse.areEmergencyChairs,
telephoneNumber: digitalGuideResponse.telephoneNumber,
surroundingId: digitalGuideResponse.surroundingId,
images: digitalGuideResponse.images,
imageUrl: imageUrl,
);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
import "package:flutter/foundation.dart";
import "package:flutter_riverpod/flutter_riverpod.dart";
import "package:riverpod_annotation/riverpod_annotation.dart";

import "../../../../../api_base_rest/client/dio_client.dart";
import "../../../../../config/env.dart";
import "../models/digital_guide_response.dart";
import "../models/digital_guide_response_extended.dart";

part "digital_guide_repository.g.dart";

@riverpod
Future<DigitalGuideResponseExtended> getDigitalGuideData(
Ref ref,
int id,
) async {
final digitalGuideUrl = "${Env.digitalGuideUrl}/buildings/$id";
final dio = ref.read(restClientProvider);
dio.options.headers["Authorization"] =
"Token ${Env.digitalGuideAuthorizationToken}";
final response = await dio.get(digitalGuideUrl);
final digitalGuideResponse =
DigitalGuideResponse.fromJson(response.data as Map<String, dynamic>);
final imageUrl = await getImageUrl(ref, digitalGuideResponse.images[0]);
return DigitalGuideResponseExtended.fromDigitalGuideResponse(
digitalGuideResponse: digitalGuideResponse,
imageUrl: imageUrl,
);
}

@riverpod
Future<String?> getImageUrl(Ref ref, int id) async {
final digitalGuideUrl = "${Env.digitalGuideUrl}/images/$id";
final dio = ref.read(restClientProvider);
dio.options.headers["Authorization"] =
"Token ${Env.digitalGuideAuthorizationToken}";

final response = await dio.get(digitalGuideUrl);

// if only fetching image url fails I want data to be presented anyway
if (response.data is! Map<String, dynamic>) {
debugPrint("Failed to fetch image url!");
return null;
}

final Map<String, dynamic> responseData =
response.data as Map<String, dynamic>;
final imageUrl = responseData["image_960w"];

return imageUrl;
}
Loading

0 comments on commit cbf1c77

Please sign in to comment.