Fins ara, l'aplicació només mostra els esdeveniments del dia actual que encara no han finalitzat. Anem a modificar-la per tal que es pugui seleccionar una data (de l'any actual o del següent) i veure tots els esdeveniments (passats o no) d'aquella data.
Comencem per crear una funció que obri el selector de dates i afegir un botó que cridi aquesta funció.
pant_principal.dart
import 'package:calendaris/data/crida_dades.dart'; import 'package:calendaris/data/dades_rebudes.dart'; import 'package:flutter/material.dart';
class PantPrincipal extends StatefulWidget {
const PantPrincipal({super.key});
@override
State<PantPrincipal> createState() => _PantPrincipalState();
}
class _PantPrincipalState extends State<PantPrincipal> {
Future<DadesRebudes?>? _rebut;
CridaDades cridaDades = CridaDades();
DateTime? _dataSeleccionada;
@override
void initState() {
super.initState();
_rebut = cridaDades.llegirDades("avui");
}
Future<void> _presentarDatePicker() async {
final DateTime ara = DateTime.now();
final DateTime? dataTriada = await showDatePicker(
context: context,
initialDate: _dataSeleccionada, // Data inicial que es mostra
firstDate: DateTime(ara.year), // La primera data que es pot seleccionar
lastDate: DateTime(ara.year + 2), // L'última data que es pot seleccionar
);
if (dataTriada != null && dataTriada != _dataSeleccionada) {
setState(() {
avui = false;
_dataSeleccionada = dataTriada;
});
}
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text("Tasques per a avui")),
body: Column(
children: [
Padding(
padding: const EdgeInsets.all(8.0),
child: Row(
children: [
TextButton(
onPressed: () {
setState(() {
_rebut = cridaDades.llegirDades();
});
},
child: Text("Avui"),
),
ElevatedButton(
onPressed: _presentarDatePicker,
child: const Text('Tria una data'),
),
],
),
),
FutureBuilder(future: _rebut, builder: (context, snapshot){
if(snapshot.connectionState == ConnectionState.waiting){
return CircularProgressIndicator();
} else if(snapshot.hasError){
return Text("Error: ${snapshot.error}");
} else if(snapshot.hasData){
return escriuResultats(snapshot.data);
} else {
return Text("No s'han trobat resultats");
}
})
],
),
);
}
}
Widget escriuResultats(DadesRebudes? dades){
if(dades != null){
if(dades.nom.isNotEmpty){
List<Widget> contingut = [];
for(int i = 0; i < dades.nom.length; i++){
Widget element = Padding(
padding: const EdgeInsets.all(8.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(dades.nom[i],
style: TextStyle(fontSize: 15, fontWeight: FontWeight.bold)),
Text("Inici: ${dades.inici[i]} h"),
Text("Final: ${dades.fi[i]} h"),
],
),
);
contingut.add(element);
}
return Padding(
padding: const EdgeInsets.all(8.0),
child: Column(
children: contingut,
),
);
}
}
return Padding(
padding: const EdgeInsets.all(8.0),
child: Text("No hi ha esdeveniments programats"),
);
}
De moment, aquest selector de dates no fa res. Ara hem de modificar la funció llegirDades per tal que tingui un paràmetre de text. Quan aquest paràmetre sigui "avui" farà el mateix que fins ara però si és una data l'enviarà compactada (AAAAMMDD) a l'script.
crida_dades.dart
import 'package:calendaris/data/claus_google.dart'; import 'package:calendaris/data/dades_rebudes.dart'; import 'package:http/http.dart' as http;
class CridaDades{
Future<DadesRebudes?> llegirDades(dataDesitjada) async{
String param = "";
if(dataDesitjada != "avui"){
String dia = dataDesitjada.day.toString();
String mes = dataDesitjada.month.toString();
String any = dataDesitjada.year.toString();
if(dia.length < 2){
dia = "0$dia";
}
if(mes.length < 2){
mes = "0$mes";
}
param = "?dia=$any$mes$dia";
}
final dadesLlegides = await http.get(Uri.parse(
"https://script.google.com/macros/s/${ClausGoogle.script}/exec$param"
));
if(dadesLlegides.statusCode == 200){
DadesRebudes dadesRebudes = DadesRebudes(nom: [], inici: [], fi: []);
return dadesRebudes.dades(dadesLlegides.body);
} else {
return null;
}
}
}
Com és lògic, també cal modificar l'script, per tal que tingui en compte el paràmetre.
// Funció per interaccionar amb el calendari des del microcontrolador
// Oriol Boix, 2025
// Sota llicència Creative Commons BY-NC-ND
// https://creativecommons.org/licenses/by-nc-nd/3.0/deed.es_ES
//
// Les variables següents ens permeten personalitzar l'script al nostre projecte
// En principi, no hauríem de tocar la resta de l'script
var idCal = "^^c20bd620d869d1d94817504b23bb4f5ea1eda1a36c32e54d92ab417e323877@group.calendar.google.com";
// Script per interactuar amb el calendari
// Funció que s'executa quan hi ha una ordre get
function doGet(e) {
var salt = "\n";
var cal = CalendarApp.getCalendarById(idCal);
if (!cal) { // Si el calendari no existeix o no tenim permís
resultat = "Calendari no trobat!";
return ContentService.createTextOutput(resultat);
}
var Dia = e.parameter.dia;
var avui = false;
if ((Dia == null) || (Dia == undefined)){
avui = true;
} else {
if(Dia.length != 8){
avui = true;
}
}
var inici = new Date(); // La data i l'hora del moment d'executar l'script
var final = new Date(); // La data i l'hora del moment d'executar l'script
if(!avui){
inici.setFullYear(Dia.substring(0, 4));
inici.setMonth(Dia.substring(4, 6) - 1);
inici.setDate(Dia.substring(6, 8));
inici.setHours(0); // Li canviem l'hora a les 0.00
inici.setMinutes(0);
final.setFullYear(Dia.substring(0, 4));
final.setMonth(Dia.substring(4, 6) - 1);
final.setDate(Dia.substring(6, 8));
}
final.setHours(23); // Li canviem l'hora a les 23.59
final.setMinutes(59);
var esdev = cal.getEvents(inici, final);
var numEsdev = esdev.length; // Quants n'hi ha?
var resultat = "";
for(var i = 0; i < numEsdev; i++){
if(i > 0){
resultat = resultat + salt;
}
resultat = resultat + dades(esdev[i]);
}
return ContentService.createTextOutput(resultat); // Enviem la resposta
}
function posaHora(dataCas) { // Afegeix, si cal, un zero
var resposta = dataCas.getHours() + ".";
if(dataCas.getMinutes() < 10){
resposta = resposta + "0";
}
resposta = resposta + dataCas.getMinutes();
return resposta;
}
function dades(esdAct) { // Organitza les dades d'un esdeveniment
var descrip = esdAct.getTitle(); // Títol de l'esdeveniment
var dataIni = esdAct.getStartTime(); // Data i hora d'inici
var dataFi = esdAct.getEndTime(); // Data i hora d'acabament
// Ens interessen només les hores d'inici i acabament
// i les volem en el format habitual en català
var ini = posaHora(dataIni);
var fi = posaHora(dataFi);
var resul = descrip + "," + ini + "," + fi;
return resul;
}
Convé recordar que cada cop que es modifica l'script cal tornar-lo a implementar i posar la nova clau al fitxer claus_google.dart.
Ara anem a fer la crida des de la pantalla principal i aprofitarem per fer alguns canvis. Farem que el títol de l'aplicació canviï indicant si les dades són d'avui o no, en quin cas es mostrarà la data corresponent.
pant_principal.dart
import 'package:calendaris/data/crida_dades.dart'; import 'package:calendaris/data/dades_rebudes.dart'; import 'package:flutter/material.dart';
class PantPrincipal extends StatefulWidget {
const PantPrincipal({super.key});
@override
State<PantPrincipal> createState() => _PantPrincipalState();
}
class _PantPrincipalState extends State<PantPrincipal> {
Future<DadesRebudes?>? _rebut;
CridaDades cridaDades = CridaDades();
DateTime? _dataSeleccionada;
bool avui = true;
@override
void initState() {
super.initState();
_rebut = cridaDades.llegirDades("avui");
}
Future<void> _presentarDatePicker() async {
final DateTime ara = DateTime.now();
final DateTime? dataTriada = await showDatePicker(
context: context,
initialDate: _dataSeleccionada, // Data inicial que es mostra
firstDate: DateTime(ara.year), // La primera data que es pot seleccionar
lastDate: DateTime(ara.year + 2), // L'última data que es pot seleccionar
);
if (dataTriada != null && dataTriada != _dataSeleccionada) {
setState(() {
avui = false;
_dataSeleccionada = dataTriada;
_rebut = cridaDades.llegirDades(_dataSeleccionada);
});
}
}
@override
Widget build(BuildContext context) {
String titol = "Tasques per a avui";
if(!avui){
titol = "Tasques per al ${_dataSeleccionada?.day}-${_dataSeleccionada?.month}-${_dataSeleccionada?.year}";
}
return Scaffold(
appBar: AppBar(title: Text(titol)),
body: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Padding(
padding: const EdgeInsets.all(8.0),
child: Row(
children: [
TextButton(
onPressed: () {
setState(() {
_rebut = cridaDades.llegirDades("avui");
_dataSeleccionada = DateTime.now();
avui = true;
});
},
child: Text("Avui"),
),
ElevatedButton(
onPressed: _presentarDatePicker,
child: const Text('Tria una data'),
),
],
),
),
FutureBuilder(future: _rebut, builder: (context, snapshot){
if(snapshot.connectionState == ConnectionState.waiting){
return CircularProgressIndicator();
} else if(snapshot.hasError){
return Text("Error: ${snapshot.error}");
} else if(snapshot.hasData){
return escriuResultats(snapshot.data);
} else {
return Text("No s'han trobat resultats");
}
})
],
),
);
}
}
Widget escriuResultats(DadesRebudes? dades){
if(dades != null){
if(dades.nom.isNotEmpty){
List<Widget> contingut = [];
for(int i = 0; i < dades.nom.length; i++){
Widget element = Padding(
padding: const EdgeInsets.all(8.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(dades.nom[i],
style: TextStyle(fontSize: 15, fontWeight: FontWeight.bold)),
Text("Inici: ${dades.inici[i]} h"),
Text("Final: ${dades.fi[i]} h"),
],
),
);
contingut.add(element);
}
return Padding(
padding: const EdgeInsets.all(8.0),
child: Column(
children: contingut,
),
);
}
}
return Padding(
padding: const EdgeInsets.all(8.0),
child: Text("No hi ha esdeveniments programats"),
);
}
Si ho provem, observarem que el calendari mostra el diumenge com a primer dia de la setmana i els noms dels dies estan en anglès. Per arreglar-ho, cal afegir la localització i posar el català com a localització actual.
Per poder emprar les localitzacions, cal anar al fitxer pubspec.yaml per dir-li que les volem fer servir. Cal posar-ho a dependencies. Al fitxer corresponent, caldrà carregar-ho.
...
dependencies:
flutter:
sdk: flutter
flutter_localizations:
sdk: flutter
...
La localització es defineix en el giny MaterialApp que tenen totes les aplicacions. Cal especificar els delegats (localizationsDelegates), les localitzacions admeses (supportedLocales) i la localització actual (locale), que pot variar durant l'execució.
main.dart
import 'package:calendaris/screens/pant_principal.dart'; import 'package:flutter/material.dart'; import 'package:flutter_localizations/flutter_localizations.dart';
void main() {
runApp(const MainApp());
}
class MainApp extends StatelessWidget {
const MainApp({super.key});
@override
Widget build(BuildContext context) {
return const MaterialApp(
locale: Locale('ca', 'ES'),
localizationsDelegates: [
GlobalMaterialLocalizations.delegate,
GlobalWidgetsLocalizations.delegate,
GlobalCupertinoLocalizations.delegate,
],
supportedLocales: [
Locale('ca'),
Locale('es'),
Locale('en'),
],
home: PantPrincipal()
);
}
}

Aquesta obra d'Oriol Boix està llicenciada sota una llicència no importada Reconeixement-NoComercial-SenseObraDerivada 3.0.