Skip to content

Commit

Permalink
Merge pull request #103 from fga-eps-mds/feat#63/tela_conteudo
Browse files Browse the repository at this point in the history
Feat#63/tela de visualizar conteúdo
  • Loading branch information
GabrielCostaDeOliveira authored Jan 30, 2025
2 parents e5adf40 + 08fb514 commit d8f256e
Show file tree
Hide file tree
Showing 15 changed files with 462 additions and 62 deletions.
2 changes: 2 additions & 0 deletions lib/core/di/locator.dart
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import 'package:aranduapp/ui/content/di/di_content.dart';
import 'package:aranduapp/core/network/token_manager/di/di_auth.dart';
import 'package:aranduapp/ui/edit_delete_user/di/di_edit_delete_user.dart';
import 'package:aranduapp/ui/edit_password/di/di_edit_password.dart';
Expand Down Expand Up @@ -25,6 +26,7 @@ void setupLocator() {
setupJourneyDI();
setupSubjectDI();
setupProfileDI();
setupContentDI();
setupAuthDI();
setupEditDeleteUser();
}
2 changes: 1 addition & 1 deletion lib/main.dart
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ class MyApp extends StatelessWidget {
theme: ThemeApp.themeData(),
darkTheme: ThemeApp.darkThemeData(),
debugShowCheckedModeBanner: false,
home: WelcomeView(),
home: const WelcomeView(),
);
}
}
6 changes: 6 additions & 0 deletions lib/ui/content/di/di_content.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import 'package:aranduapp/ui/content/viewmodel/content_viewmodel.dart';
import 'package:get_it/get_it.dart';

void setupContentDI() {
GetIt.I.registerFactory(() => ContentViewModel());
}
28 changes: 28 additions & 0 deletions lib/ui/content/model/content_request.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import 'dart:convert';

class ContentRequest {
final String title;
final String content;
final String trailID;

ContentRequest(
{required this.title, required this.content, required this.trailID});

Map<String, dynamic> toJson() {
return <String, dynamic>{
'title': title,
'content': content,
'trailID': trailID,
};
}

factory ContentRequest.fromJsonString(String jsonString) {
final json = jsonDecode(jsonString);

return ContentRequest(
title: json['title']! as String,
content: json['content'] as String,
trailID: json['trailID'] as String,
);
}
}
33 changes: 33 additions & 0 deletions lib/ui/content/model/content_response.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
class ContentResponse {
final String id;
final String title;
final String content;
final String trail;
final int order;
final DateTime createdAt;
final DateTime updatedAt;

ContentResponse({
required this.id,
required this.title,
required this.content,
required this.trail,
required this.order,
required this.createdAt,
required this.updatedAt,
});

factory ContentResponse.fromJson(Map<String, dynamic> json) {
return ContentResponse(
id: json['_id'],
title: json['title'],
content: json['content'],
trail: json['trail'],
order: json['order'],
createdAt: DateTime.parse(json['createdAt']),
updatedAt: DateTime.parse(json['updatedAt']),
);
}

static fromJsonString(contentJson) {}
}
37 changes: 37 additions & 0 deletions lib/ui/content/service/content_service.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import 'package:aranduapp/core/log/log.dart';
import 'package:aranduapp/core/network/studio_maker_api.dart';
import 'package:dio/dio.dart';

class ContentService {
Future<List<Map<String, dynamic>>?> getContentsByTrail(String trailId) async {
try {
String path = '/contents/trail/$trailId';
Response response = await StudioMakerApi.getInstance().get(path: path);
if (response.statusCode == 200 && response.data != null) {
return List<Map<String, dynamic>>.from(response.data);
} else {
Log.e('Erro ao buscar conteúdos da trilha:${response.statusCode}');
return null;
}
} catch (e) {
Log.e('Erro na requisição $e');
return null;
}
}

Future<Map<String, dynamic>?> getContentsById(String contentId) async {
try {
String path = '/contents/$contentId';
Response response = await StudioMakerApi.getInstance().get(path: path);
if (response.statusCode == 200 && response.data != null) {
return Map<String, dynamic>.from(response.data);
} else {
Log.e('Erro ao buscar conteúdos da trilha:${response.statusCode}');
return null;
}
} catch (e) {
Log.e('Erro na requisição $e');
return null;
}
}
}
178 changes: 178 additions & 0 deletions lib/ui/content/view/content_view.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,178 @@
import 'package:flutter/material.dart';
import 'package:flutter_markdown/flutter_markdown.dart';
import 'package:flutter_markdown_latex/flutter_markdown_latex.dart';
import 'package:markdown/markdown.dart' as md;
import 'package:provider/provider.dart';
import 'package:aranduapp/ui/content/service/content_service.dart';
import 'package:aranduapp/ui/content/viewmodel/content_viewmodel.dart';

class ContentView extends StatelessWidget {
final String contentID;

const ContentView({super.key, required this.contentID});

@override
Widget build(BuildContext context) {
final ContentService contentService = ContentService();

return ChangeNotifierProvider(
create: (context) => ContentViewModel(),
child: Scaffold(
appBar: AppBar(
title: FutureBuilder<Map<String, dynamic>?>(
future: contentService.getContentsById(contentID),
builder: (context, snapshot) {
if (snapshot.connectionState == ConnectionState.waiting) {
return const Text("Carregando...");
} else if (snapshot.hasError) {
return const Text("Erro");
} else if (!snapshot.hasData || snapshot.data!.isEmpty) {
return const Text("Conteúdo não encontrado");
}
final content = snapshot.data!;
return Text(content['title']);
},
),
backgroundColor: Theme.of(context).colorScheme.onPrimary,
elevation: 0,
foregroundColor: Theme.of(context).colorScheme.onSurface,
leading: IconButton(
icon: const Icon(Icons.arrow_back),
onPressed: () {
Navigator.pop(context);
},
color: Theme.of(context).colorScheme.onSurface,
),
),
body: FutureBuilder<Map<String, dynamic>?>(
future: contentService.getContentsById(contentID),
builder: (context, snapshot) {
if (snapshot.connectionState == ConnectionState.waiting) {
return const Center(child: CircularProgressIndicator());
} else if (snapshot.hasError) {
return const Center(child: Text("Erro ao carregar conteúdo"));
} else if (!snapshot.hasData || snapshot.data!.isEmpty) {
return const Center(child: Text("Conteúdo não encontrado"));
}

final content = snapshot.data!;

return Consumer<ContentViewModel>(
builder: (context, viewModel, child) {
return Column(
children: [
// Barra de progresso
SizedBox(
height: 10.0,
child: LinearProgressIndicator(
value: viewModel.progress,
backgroundColor: Colors.grey[300],
valueColor: AlwaysStoppedAnimation<Color>(
Theme.of(context).colorScheme.primary,
),
),
),
Expanded(
child: SingleChildScrollView(
controller: viewModel.scrollController,
padding: const EdgeInsets.all(16.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.center,
children: [
_renderMarkdown(content['content']),
// Botão "Finalizar" sempre visível no final
Padding(
padding:
const EdgeInsets.symmetric(vertical: 16.0),
child: ElevatedButton(
style: ElevatedButton.styleFrom(
backgroundColor:
Theme.of(context).colorScheme.primary,
),
onPressed: () {
_showCompletionDialog(context);
},
child: Text(
'Finalizar',
style: Theme.of(context)
.textTheme
.bodyLarge
?.apply(
color: Theme.of(context)
.colorScheme
.onPrimary,
),
),
),
),
],
),
),
),
],
);
},
);
},
),
),
);
}

Widget _renderMarkdown(String markdownContent) {
return MarkdownBody(
data: markdownContent,
builders: {
'latex': LatexElementBuilder(),
},
extensionSet: md.ExtensionSet(
[LatexBlockSyntax()],
[LatexInlineSyntax()],
),
);
}

void _showCompletionDialog(BuildContext context) {
showDialog(
context: context,
builder: (BuildContext dialogContext) {
return AlertDialog(
content: SizedBox(
width: 250,
height: 150,
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Text(
"Conteúdo Finalizado! Voltando para a trilha.",
textAlign: TextAlign.center,
style: Theme.of(context)
.textTheme
.bodyLarge
?.apply(color: Theme.of(context).colorScheme.onSurface),
),
const SizedBox(height: 20),
ElevatedButton(
style: ElevatedButton.styleFrom(
backgroundColor: Theme.of(context).colorScheme.primary,
),
onPressed: () {
Navigator.of(dialogContext).pop();
Navigator.of(context).pop();
},
child: Text(
'Trilha',
style: Theme.of(context).textTheme.bodyLarge?.apply(
color: Theme.of(context).colorScheme.onPrimary,
),
),
)
],
),
),
);
},
);
}
}
30 changes: 30 additions & 0 deletions lib/ui/content/viewmodel/content_viewmodel.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import 'package:flutter/material.dart';

class ContentViewModel extends ChangeNotifier {
final ScrollController scrollController = ScrollController();
double _progress = 0.0;

double get progress => _progress;

bool get shouldShowButton => _progress == 1.0;

ContentViewModel() {
scrollController.addListener(_updateProgress);
}

void _updateProgress() {
if (scrollController.hasClients) {
final maxScrollExtent = scrollController.position.maxScrollExtent;
final currentScroll = scrollController.position.pixels;
_progress = (currentScroll / maxScrollExtent).clamp(0.0, 1.0);
notifyListeners(); // Notifica as views sobre a atualização
}
}

@override
void dispose() {
scrollController.removeListener(_updateProgress);
scrollController.dispose();
super.dispose();
}
}
2 changes: 1 addition & 1 deletion lib/ui/journey/view/journey_view.dart
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ class _JourneyScreen extends StatelessWidget {
if (viewModel.getJourneyCommand.isOk) {
return _buildListView(context);
} else if (viewModel.getJourneyCommand.isError) {
return const ErrorScreen(message: "Deslize para baixo");
return ErrorScreen(message: "Deslize para baixo\n\n ${viewModel.getJourneyCommand.result!.asError!.error.toString()}");
} else {
return const LoadingWidget();
}
Expand Down
17 changes: 7 additions & 10 deletions lib/ui/welcome/view/welcome_view.dart
Original file line number Diff line number Diff line change
@@ -1,10 +1,9 @@
import 'package:aranduapp/ui/onboarding/view/onboarding_view.dart';
import 'package:flutter/material.dart';
import 'package:flutter/rendering.dart';
import 'package:google_fonts/google_fonts.dart';

class WelcomeView extends StatefulWidget {
WelcomeView({super.key});
const WelcomeView({super.key});

@override
State<WelcomeView> createState() => _WelcomeViewState();
Expand Down Expand Up @@ -53,14 +52,12 @@ class _WelcomeViewState extends State<WelcomeView> {
),
// Imagem com deslocamento para a esquerda
Transform.translate(
offset: Offset(-10, 0), // Move 30 pixels para a esquerda
child: Container(
child: Image.asset(
'assets/images/Logo.png',
width: circleDiameter * 0.24,
height: circleDiameter * 0.24,
fit: BoxFit.contain,
),
offset: const Offset(-10, 0), // Move 30 pixels para a esquerda
child: Image.asset(
'assets/images/Logo.png',
width: circleDiameter * 0.24,
height: circleDiameter * 0.24,
fit: BoxFit.contain,
),
),
],
Expand Down
Loading

0 comments on commit d8f256e

Please sign in to comment.