Przeglądaj źródła

FOTOBOEK: eerste plek werkend — logo kiezen uit Drupal-fotoboek

Op uitgaansevenementAanmaken kun je nu een logo kiezen uit je eigen
Drupal-fotoboek, naast de bestaande upload via camera/galerij.

- API Call MijnFotos (cookie-header, Predefined Path `items` = null-veilig)
- Page states fotoboekOpen (bool) + fotoboekDoel (String)
- Knop "Uit fotoboek" (toggle) naast "Logo kiezen", met EN-vertaling
- GridView 3 kolommen op $.thumb; tik zet createLogoFid=$.fid en
  createLogoUrl=$.url en sluit het blok
- Lege staat via LegeLijstComponent, gecentreerd
- Visibility op de Container (niet op het kind), anders blijft er 300 dp
  witruimte in het formulier staan

Getest op emulator-5554 (profile): grid laadt, keuze vult de preview,
blok sluit, geen witruimte. dart analyze 0 errors.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
bob 1 godzinę temu
rodzic
commit
a0035e7f92

+ 38 - 0
CLAUDE.md

@@ -1477,6 +1477,13 @@ opgegeven). Drie dingen lopen bij hetzelfde veld door elkaar:
    overlay net gesloten is.
 3. Het icoontje naast het label "Value to set" is **niet** de variabele-kiezer —
    de tooltip zegt "Clear Default Value".
+- **✅ Genuanceerd 2026-09-22 (fotoboek): bij een `Update Page State`-actie IS dat
+  ⚏-icoontje naast "Value to set" juist wél de variabele-kiezer, en het is daar de
+  enige route die werkt.** Een klik op het `Unset`-veld zelf zet het veld in
+  tekstinvoer-modus (punt 1 hierboven); een klik op het ⚏ ernaast opent netjes de
+  "Set Variable"-dialoog. Drie velden (twee JSON Paths + een boolean) gingen zo in
+  één ronde goed. Probeer bij een dood lijkend veld dus altijd **beide** plekken vóór
+  je opgeeft — welke van de twee werkt, hangt af van het actietype.
 **Praktisch: een actie-argument dat een custom function met meerdere argumenten
 nodig heeft, is via de Action Flow Editor voor Claude niet te zetten.** Ruim je
 halve actie op (⋮ op de actie-node → *Delete Action*) vóór je verdergaat: een
@@ -2234,6 +2241,37 @@ bevestigde pogingen stoppen en aan Bob vragen — geef het exacte pad
   ook op de plekken waar nu nog een kaal `logo800px.png`/applogo als lege staat
   staat. **Let op:** een parameterwaarde is een letterlijke string, geen
   i18n-sleutel, dus zo'n melding krijgt géén globe en dus geen EN-vertaling.
+- **Recept dat 2026-09-22 in één ronde werkte (op een `GridView`):** widget
+  selecteren → Styling-tab → eigenschappen-filter `Empty` → **Show Empty List
+  Widget** aan → filter wissen (anders verbergt het de vervolgopties, wat eruitziet
+  alsof de toggle niets deed) → sectie **Empty List Widget** uitklappen → *Widget
+  Type* **Component** → *Select Component* → zoeken → de parameterrij `tekst *`
+  aanklikken → Value typen (`triple_click` + typen, commit met een klik op het
+  label "Parameters") → **Center Component** aan. Export:
+  `return LegeLijstComponentWidget(tekst: '…');`
+
+**⚠️ Een blok verbergen met een Visibility-conditie op het KIND laat de lege
+ruimte van de ouder staan — zet de conditie op de Container met de vaste
+hoogte.** Bevestigd 2026-09-22 op het fotoboek-blok: de conditie stond op de
+`Column` ín een `Container(height: 300)`, en dat gaf `visible: x` — de Column
+verdwijnt, maar de Container reserveert nog steeds 300 dp, dus je houdt een
+groot wit vlak midden in het formulier. `dart analyze` zegt daar niets over en
+op het canvas valt het nauwelijks op. Dezelfde conditie op de **Container**
+genereert `if (x)` (de widget wordt helemaal niet gebouwd) en de ruimte is weg.
+Vuistregel: staat er een vaste `Height`/`Width` op de ouder, dan hoort de
+conditie dáár. De oude conditie op het kind mag blijven staan (onschadelijk, en
+een Conditional uitzetten wist de conditie — zie elders).
+
+**Een knop met identieke styling naast een bestaande knop: dupliceren en de
+actieketen weggooien.** Sneller dan een verse `Button` inserten plus alle
+kleuren/padding/radius overtypen, en de kopie landt automatisch **direct ná het
+origineel** in dezelfde `Row`. Recept: rechtsklik de knop in de tree →
+*Duplicate* → de **tweede** rij aanklikken (dat is de kopie) → hernoemen →
+Actions-tab → **⋮ naast de trigger → *Delete Action Chain*** → nieuwe actie
+opbouwen. ⚠️ Doe dat wegggooien **meteen**: een gekopieerde keten met een *Action
+Output Variable Name* declareert die naam een tweede keer, wat de Issues-teller
+direct omhoog gooit (hier 2 → 6). En de kopie erft de **Engelse vertaling** van
+het origineel, dus loop daarna even langs de globe.
 
 **Patroon: lege/ontbrekende afbeeldings-URL laat de app crashen.**
 `CachedNetworkImage` gooit een **synchrone** `ArgumentError` bij het

+ 1 - 1
TASKS.md

@@ -26,7 +26,7 @@ Google Play en één doorloop die alleen Bob kan doen.
 
 | # | Taak | Kort |
 |---|---|---|
-| **FOTOBOEK** | Fotoboek: foto's kiezen uit Drupal + beheren | **Claude — bezig** (sessie 2026-09-22). Drupal-kant **AF** (endpoint `mijn_fotos` index+verwijder, live op devbob én productie, getest). App-kant in aanbouw: keuzeblok met Camera/Galerij/Fotoboek op de 4 upload-plekken (`uitgaansevenementAanmaken` + `stadsactiviteitAanmaken`, elk logo + foto's) en een 5e tab "Mijn foto's" op `mijnProfiel` voor beheer/verwijderen. Volledig plan + endpoint-spec staan in `CLAUDE.md` (commit 8c73621) |
+| **FOTOBOEK** | Fotoboek: foto's kiezen uit Drupal + beheren | **Claude — bezig** (sessie 2026-09-22). Drupal-kant **AF** (endpoint `mijn_fotos` index+verwijder, live op devbob én productie, getest). App-kant: **eerste plek werkend** — logo op `uitgaansevenementAanmaken`. API Call `MijnFotos` (Predefined Path `items`, cookie-header), page states `fotoboekOpen`/`fotoboekDoel`, knop *Uit fotoboek* (toggle) naast *Logo kiezen*, en een `GridView` (3 kolommen, `$.thumb`) waarvan een tik `createLogoFid`=`$.fid` + `createLogoUrl`=`$.url` zet en het blok sluit. `dart analyze` 0 errors. **Rest:** omzetten naar herbruikbaar component en uitrollen naar de 3 andere upload-plekken (foto's hier + logo/foto's op `stadsactiviteitAanmaken`), Camera/Galerij-knoppen in hetzelfde blok, 5e tab "Mijn foto's" op `mijnProfiel` voor beheer/verwijderen (custom function `fotoVerwijderBody`), lege staat via `LegeLijstComponent` + vertalingen. Volledig plan + endpoint-spec in `CLAUDE.md` (commit 8c73621) |
 | **133** | Kopie-knop verkleinen (mijnProfiel 2x + MijnAanmeldingen) | **Claude — bezig** (plan af 2026-09-22, wacht op groen licht voor de browser). Variant A (icoon in de rij) op `mijnProfiel`, variant B (compacte chip) op `MijnAanmeldingen`, tooltip erbij, kleuren meteen goed. Volledig plan + terugvalopties onderaan dit bestand |
 | **G0.6** | Play-screenshots | ✅ Nieuwe set van 10 klaar (2026-09-19 avond). Alleen `tel-04-horeca.png` moet over zodra *Show Test Ads* uit staat |
 

+ 35 - 0
lib/backend/api_requests/api_calls.dart

@@ -1169,6 +1169,41 @@ class NieuwsbrievenCall {
       ) as List?;
 }
 
+class MijnFotosCall {
+  static Future<ApiCallResponse> call({
+    String? sessionName = '',
+    String? sessid = '',
+    int? page = 0,
+    int? limit = 30,
+  }) async {
+    return ApiManager.instance.makeApiCall(
+      callName: 'MijnFotos',
+      apiUrl: 'https://uitgaanskrant.com/nl/flutterdrup/mijn_fotos.json',
+      callType: ApiCallType.GET,
+      headers: {
+        'Cookie': '${sessionName}=${sessid}',
+        'Accept': 'application/json',
+      },
+      params: {
+        'page': page,
+        'limit': limit,
+      },
+      returnBody: true,
+      encodeBodyUtf8: false,
+      decodeUtf8: false,
+      cache: false,
+      isStreamingApi: false,
+      alwaysAllowBody: false,
+    );
+  }
+
+  static List? items(dynamic response) => getJsonField(
+        response,
+        r'''$[:]''',
+        true,
+      ) as List?;
+}
+
 class ApiPagingParams {
   int nextPageNumber = 0;
   int numItems = 0;

+ 4 - 0
lib/flutter_flow/internationalization.dart

@@ -1000,6 +1000,10 @@ final kTranslationsMap = <Map<String, Map<String, String>>>[
       'nl': 'Logo kiezen',
       'en': 'Select logo',
     },
+    'ml0k1fmv': {
+      'nl': 'Uit fotoboek',
+      'en': 'From my photos',
+    },
     'hqwm9set': {
       'nl': 'Foto\'s kiezen',
       'en': 'Select photos',

+ 37 - 0
lib/uitgaansevenement_aanmaken/uitgaansevenement_aanmaken_model.dart

@@ -7,6 +7,7 @@ import '/flutter_flow/flutter_flow_widgets.dart';
 import '/flutter_flow/form_field_controller.dart';
 import '/flutter_flow/upload_data.dart';
 import '/shared/drawer_component/drawer_component_widget.dart';
+import '/shared/lege_lijst_component/lege_lijst_component_widget.dart';
 import 'dart:ui';
 import '/custom_code/actions/index.dart' as actions;
 import '/flutter_flow/custom_functions.dart' as functions;
@@ -116,6 +117,22 @@ class UitgaansevenementAanmakenModel
   void updateCreateFotosUrlsAtIndex(int index, Function(String) updateFn) =>
       createFotosUrls[index] = updateFn(createFotosUrls[index]);
 
+  bool _fotoboekOpen = false;
+  set fotoboekOpen(bool value) {
+    _fotoboekOpen = value;
+    debugLogWidgetClass(this);
+  }
+
+  bool get fotoboekOpen => _fotoboekOpen;
+
+  String _fotoboekDoel = '';
+  set fotoboekDoel(String value) {
+    _fotoboekDoel = value;
+    debugLogWidgetClass(this);
+  }
+
+  String get fotoboekDoel => _fotoboekDoel;
+
   ///  State fields for stateful widgets in this page.
 
   // Stores action output result for [Backend Call - API (MijnHorecagelegenheden)] action in uitgaansevenementAanmaken widget.
@@ -374,6 +391,26 @@ class UitgaansevenementAanmakenModel
                 'reference=QiIKGAoPY3JlYXRlRm90b3NVcmxzEgVjZDhwNXIGEgIIBCABUAFaD2NyZWF0ZUZvdG9zVXJsc2IZdWl0Z2FhbnNldmVuZW1lbnRBYW5tYWtlbg==',
             name: 'String',
             nullable: false,
+          ),
+          'fotoboekOpen': debugSerializeParam(
+            fotoboekOpen,
+            ParamType.bool,
+            link:
+                'https://app.flutterflow.io/project/uitgaanskrant-1qhvtd?tab=uiBuilder&page=uitgaansevenementAanmaken',
+            searchReference:
+                'reference=QiYKFQoMZm90b2JvZWtPcGVuEgV5MTlhYioHEgVmYWxzZXIECAUgAVABWgxmb3RvYm9la09wZW5iGXVpdGdhYW5zZXZlbmVtZW50QWFubWFrZW4=',
+            name: 'bool',
+            nullable: false,
+          ),
+          'fotoboekDoel': debugSerializeParam(
+            fotoboekDoel,
+            ParamType.String,
+            link:
+                'https://app.flutterflow.io/project/uitgaanskrant-1qhvtd?tab=uiBuilder&page=uitgaansevenementAanmaken',
+            searchReference:
+                'reference=QiEKFQoMZm90b2JvZWtEb2VsEgV2dTNpeSoCEgByBAgDIAFQAVoMZm90b2JvZWtEb2VsYhl1aXRnYWFuc2V2ZW5lbWVudEFhbm1ha2Vu',
+            name: 'String',
+            nullable: false,
           )
         }.entries,
         widgetStates: {

+ 202 - 0
lib/uitgaansevenement_aanmaken/uitgaansevenement_aanmaken_widget.dart

@@ -7,6 +7,7 @@ import '/flutter_flow/flutter_flow_widgets.dart';
 import '/flutter_flow/form_field_controller.dart';
 import '/flutter_flow/upload_data.dart';
 import '/shared/drawer_component/drawer_component_widget.dart';
+import '/shared/lege_lijst_component/lege_lijst_component_widget.dart';
 import 'dart:ui';
 import '/custom_code/actions/index.dart' as actions;
 import '/flutter_flow/custom_functions.dart' as functions;
@@ -2775,6 +2776,50 @@ class _UitgaansevenementAanmakenWidgetState
                                 ),
                               ),
                             ),
+                            Align(
+                              alignment: AlignmentDirectional(-1.0, 0.0),
+                              child: FFButtonWidget(
+                                onPressed: () async {
+                                  _model.fotoboekOpen = !_model.fotoboekOpen;
+                                  safeSetState(() {});
+                                },
+                                text: FFLocalizations.of(context).getText(
+                                  'ml0k1fmv' /* Uit fotoboek */,
+                                ),
+                                options: FFButtonOptions(
+                                  height: 40.0,
+                                  padding: EdgeInsetsDirectional.fromSTEB(
+                                      16.0, 0.0, 16.0, 0.0),
+                                  iconPadding: EdgeInsetsDirectional.fromSTEB(
+                                      0.0, 0.0, 0.0, 0.0),
+                                  color: FlutterFlowTheme.of(context).secondary,
+                                  textStyle: FlutterFlowTheme.of(context)
+                                      .titleSmall
+                                      .override(
+                                        font: GoogleFonts.roboto(
+                                          fontWeight:
+                                              FlutterFlowTheme.of(context)
+                                                  .titleSmall
+                                                  .fontWeight,
+                                          fontStyle:
+                                              FlutterFlowTheme.of(context)
+                                                  .titleSmall
+                                                  .fontStyle,
+                                        ),
+                                        color: Colors.white,
+                                        letterSpacing: 0.0,
+                                        fontWeight: FlutterFlowTheme.of(context)
+                                            .titleSmall
+                                            .fontWeight,
+                                        fontStyle: FlutterFlowTheme.of(context)
+                                            .titleSmall
+                                            .fontStyle,
+                                      ),
+                                  elevation: 0.0,
+                                  borderRadius: BorderRadius.circular(8.0),
+                                ),
+                              ),
+                            ),
                             if (_model.createLogoUrl != null &&
                                 _model.createLogoUrl != '')
                               ClipRRect(
@@ -2977,6 +3022,163 @@ class _UitgaansevenementAanmakenWidgetState
                               ),
                           ],
                         ),
+                        if (_model.fotoboekOpen)
+                          Padding(
+                            padding: EdgeInsetsDirectional.fromSTEB(
+                                0.0, 12.0, 0.0, 0.0),
+                            child: Container(
+                              height: 300.0,
+                              decoration: BoxDecoration(),
+                              child: Visibility(
+                                visible: _model.fotoboekOpen,
+                                child: Column(
+                                  mainAxisSize: MainAxisSize.max,
+                                  children: [
+                                    Expanded(
+                                      child: FutureBuilder<ApiCallResponse>(
+                                        future: MijnFotosCall.call(
+                                          sessionName:
+                                              FFAppState().userSessionname,
+                                          sessid: FFAppState().userSessionid,
+                                        ),
+                                        builder: (context, snapshot) {
+                                          // Customize what your widget looks like when it's loading.
+                                          if (!snapshot.hasData) {
+                                            return Center(
+                                              child: SizedBox(
+                                                width: 80.0,
+                                                height: 80.0,
+                                                child: SpinKitFadingCircle(
+                                                  color: FlutterFlowTheme.of(
+                                                          context)
+                                                      .primary,
+                                                  size: 80.0,
+                                                ),
+                                              ),
+                                            );
+                                          }
+                                          final gridViewMijnFotosResponse =
+                                              snapshot.data!;
+                                          _model.debugBackendQueries[
+                                                  'MijnFotosCall_statusCode_GridView_pu3jv77b'] =
+                                              debugSerializeParam(
+                                            gridViewMijnFotosResponse
+                                                .statusCode,
+                                            ParamType.int,
+                                            link:
+                                                'https://app.flutterflow.io/project/uitgaanskrant-1qhvtd?tab=uiBuilder&page=uitgaansevenementAanmaken',
+                                            name: 'int',
+                                            nullable: false,
+                                          );
+                                          _model.debugBackendQueries[
+                                                  'MijnFotosCall_responseBody_GridView_pu3jv77b'] =
+                                              debugSerializeParam(
+                                            gridViewMijnFotosResponse.bodyText,
+                                            ParamType.String,
+                                            link:
+                                                'https://app.flutterflow.io/project/uitgaanskrant-1qhvtd?tab=uiBuilder&page=uitgaansevenementAanmaken',
+                                            name: 'String',
+                                            nullable: false,
+                                          );
+                                          debugLogWidgetClass(_model);
+
+                                          return Builder(
+                                            builder: (context) {
+                                              final fotoboekItem =
+                                                  MijnFotosCall.items(
+                                                        gridViewMijnFotosResponse
+                                                            .jsonBody,
+                                                      )?.toList() ??
+                                                      [];
+                                              if (fotoboekItem.isEmpty) {
+                                                return Center(
+                                                  child:
+                                                      LegeLijstComponentWidget(
+                                                    tekst:
+                                                        'Je hebt nog geen foto\'s. Upload er een met Logo kiezen.',
+                                                  ),
+                                                );
+                                              }
+                                              _model.debugGeneratorVariables[
+                                                      'fotoboekItem${fotoboekItem.length > 100 ? ' (first 100)' : ''}'] =
+                                                  debugSerializeParam(
+                                                fotoboekItem.take(100),
+                                                ParamType.JSON,
+                                                isList: true,
+                                                link:
+                                                    'https://app.flutterflow.io/project/uitgaanskrant-1qhvtd?tab=uiBuilder&page=uitgaansevenementAanmaken',
+                                                name: 'dynamic',
+                                                nullable: false,
+                                              );
+                                              debugLogWidgetClass(_model);
+
+                                              return GridView.builder(
+                                                padding: EdgeInsets.zero,
+                                                gridDelegate:
+                                                    SliverGridDelegateWithFixedCrossAxisCount(
+                                                  crossAxisCount: 3,
+                                                  crossAxisSpacing: 10.0,
+                                                  mainAxisSpacing: 10.0,
+                                                  childAspectRatio: 1.0,
+                                                ),
+                                                scrollDirection: Axis.vertical,
+                                                itemCount: fotoboekItem.length,
+                                                itemBuilder: (context,
+                                                    fotoboekItemIndex) {
+                                                  final fotoboekItemItem =
+                                                      fotoboekItem[
+                                                          fotoboekItemIndex];
+                                                  return InkWell(
+                                                    splashColor:
+                                                        Colors.transparent,
+                                                    focusColor:
+                                                        Colors.transparent,
+                                                    hoverColor:
+                                                        Colors.transparent,
+                                                    highlightColor:
+                                                        Colors.transparent,
+                                                    onTap: () async {
+                                                      _model.createLogoFid =
+                                                          getJsonField(
+                                                        fotoboekItemItem,
+                                                        r'''$.fid''',
+                                                      ).toString();
+                                                      _model.createLogoUrl =
+                                                          getJsonField(
+                                                        fotoboekItemItem,
+                                                        r'''$.url''',
+                                                      ).toString();
+                                                      _model.fotoboekOpen =
+                                                          false;
+                                                      safeSetState(() {});
+                                                    },
+                                                    child: ClipRRect(
+                                                      borderRadius:
+                                                          BorderRadius.circular(
+                                                              8.0),
+                                                      child: Image.network(
+                                                        getJsonField(
+                                                          fotoboekItemItem,
+                                                          r'''$.thumb''',
+                                                        ).toString(),
+                                                        width: double.infinity,
+                                                        height: double.infinity,
+                                                        fit: BoxFit.cover,
+                                                      ),
+                                                    ),
+                                                  );
+                                                },
+                                              );
+                                            },
+                                          );
+                                        },
+                                      ),
+                                    ),
+                                  ],
+                                ),
+                              ),
+                            ),
+                          ),
                       ],
                     ),
                   ),