Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
fc31949d84 | ||
|
|
95e496134b | ||
|
|
2b429cb36d | ||
|
|
8eb0badd5d | ||
|
|
5b791d5244 | ||
|
|
a0d36083e1 | ||
|
|
a206bde3c5 | ||
|
|
44064d2ea2 | ||
|
|
88a89bfa01 | ||
|
|
4918df574a | ||
|
|
706403ba31 |
@@ -0,0 +1,61 @@
|
|||||||
|
-- MijnBesluit - de versiehistorie als changelog voor SideKick
|
||||||
|
--
|
||||||
|
-- SideKick toont op het tabblad Changelog de regels van een tabel of view met een vast formaat
|
||||||
|
-- (zie de SideKick-handleiding, sectie 5: Datum, Versie, Product, Soort, Onderwerp, Inhoud, Actief,
|
||||||
|
-- Aanmaakdatum, Updatedatum). MijnBesluit heeft geen tweede changelog: de versiehistorie
|
||||||
|
-- (version_history, onderhouden op de website onder Beheer > Versiehistorie) IS de changelog.
|
||||||
|
-- Deze view geeft haar in dat formaat. SideKick bewerkt niets; de agent op de server leest alleen.
|
||||||
|
--
|
||||||
|
-- Datum datum
|
||||||
|
-- Versie versie, leeg als het alleen de website betreft
|
||||||
|
-- Product vast: MijnBesluit (app en website samen)
|
||||||
|
-- Soort vast: Nieuw (de versiehistorie bevat alleen nieuwe of wezenlijk gewijzigde
|
||||||
|
-- functies, geen bugfixes)
|
||||||
|
-- Onderwerp de kop (per datum uniek, daar zorgt version_history voor)
|
||||||
|
-- Inhoud de uitleg, platte tekst
|
||||||
|
-- Actief zichtbaar (0 = verborgen)
|
||||||
|
-- Aanmaakdatum aangemaakt_op (UTC)
|
||||||
|
-- Updatedatum gewijzigd_op (UTC); de website zet hem bij elke wijziging opnieuw, ook bij
|
||||||
|
-- verbergen of tonen. Een verwijderde regel valt weg; SideKick merkt dat aan het
|
||||||
|
-- afwijkende aantal en leest dan alles opnieuw.
|
||||||
|
--
|
||||||
|
-- De view draait met de rechten van wie hem maakt (root, SQL SECURITY DEFINER). Wie hem leest hoeft
|
||||||
|
-- dus alleen SELECT op de view te hebben, niet op version_history.
|
||||||
|
--
|
||||||
|
-- Eenmalig te draaien als root, ná db\22_versiehistorie.sql:
|
||||||
|
-- mysql -u root -p mijnbesluit < db\handmatig\22c_serverchangelog_view.sql
|
||||||
|
--
|
||||||
|
-- Opnieuw draaien mag (CREATE OR REPLACE). Het staat in handmatig\ omdat het uitrolaccount wel
|
||||||
|
-- tabellen maar niet per se views mag maken, en geen rechten mag geven.
|
||||||
|
--
|
||||||
|
-- Daarna in SideKick bij de agent van MijnBesluit: "Changelog: tabel" = serverchangelog, met
|
||||||
|
-- DbNaam = mijnbesluit en een gebruiker die de view mag lezen (zie onderaan).
|
||||||
|
|
||||||
|
USE `mijnbesluit`;
|
||||||
|
|
||||||
|
CREATE OR REPLACE
|
||||||
|
SQL SECURITY DEFINER
|
||||||
|
VIEW `serverchangelog` AS
|
||||||
|
SELECT
|
||||||
|
`datum` AS `Datum`,
|
||||||
|
COALESCE(`versie`, '') AS `Versie`,
|
||||||
|
'MijnBesluit' AS `Product`,
|
||||||
|
'Nieuw' AS `Soort`,
|
||||||
|
`kop` AS `Onderwerp`,
|
||||||
|
`tekst` AS `Inhoud`,
|
||||||
|
`zichtbaar` AS `Actief`,
|
||||||
|
`aangemaakt_op` AS `Aanmaakdatum`,
|
||||||
|
`gewijzigd_op` AS `Updatedatum`
|
||||||
|
FROM `version_history`;
|
||||||
|
|
||||||
|
-- De gebruiker die de agent gebruikt om te lezen. Dit geeft de bestaande applicatiegebruiker leesrecht
|
||||||
|
-- op alleen de view. Liever een eigen leesgebruiker? Maak die zelf aan met een wachtwoord dat alleen
|
||||||
|
-- jij kent en geef hem hetzelfde recht, bijvoorbeeld:
|
||||||
|
-- CREATE USER 'mijnbesluit_changelog'@'localhost' IDENTIFIED BY '<kies-zelf-een-wachtwoord>';
|
||||||
|
-- GRANT SELECT ON `mijnbesluit`.`serverchangelog` TO 'mijnbesluit_changelog'@'localhost';
|
||||||
|
GRANT SELECT ON `mijnbesluit`.`serverchangelog` TO 'mijnbesluit_app'@'localhost';
|
||||||
|
FLUSH PRIVILEGES;
|
||||||
|
|
||||||
|
-- Controle: de regels van de versiehistorie, nieuwste bovenaan.
|
||||||
|
SELECT `Datum`, `Versie`, `Product`, `Soort`, `Onderwerp`, `Actief`, `Updatedatum`
|
||||||
|
FROM `serverchangelog` ORDER BY `Datum` DESC, `Aanmaakdatum` DESC LIMIT 20;
|
||||||
@@ -31,9 +31,26 @@ geen tekstuele correcties, geen opmaak die is rechtgezet.
|
|||||||
2. Als root: `mysql -u root -p < db\02_applicatiegebruiker.sql` (rechten voor `mijnbesluit_app`
|
2. Als root: `mysql -u root -p < db\02_applicatiegebruiker.sql` (rechten voor `mijnbesluit_app`
|
||||||
op de nieuwe tabel) en `mysql -u root -p mijnbesluit < db\handmatig\22b_versiehistorie_startrijen.sql`
|
op de nieuwe tabel) en `mysql -u root -p mijnbesluit < db\handmatig\22b_versiehistorie_startrijen.sql`
|
||||||
(de bestaande regels; `INSERT IGNORE`, dus opnieuw draaien is veilig).
|
(de bestaande regels; `INSERT IGNORE`, dus opnieuw draaien is veilig).
|
||||||
3. Daarna pas de website en de SocketServer uitrollen. Andersom leest de nieuwe code een tabel
|
3. Voor de changelog in SideKick (optioneel, kan ook later): als root
|
||||||
|
`mysql -u root -p mijnbesluit < db\handmatig\22c_serverchangelog_view.sql` (zie hieronder).
|
||||||
|
4. Daarna pas de website en de SocketServer uitrollen. Andersom leest de nieuwe code een tabel
|
||||||
die er nog niet is, of waar de applicatiegebruiker nog niet bij mag.
|
die er nog niet is, of waar de applicatiegebruiker nog niet bij mag.
|
||||||
|
|
||||||
|
## Changelog in SideKick
|
||||||
|
|
||||||
|
Het tabblad Changelog van SideKick leest een tabel of view `serverchangelog` met vaste kolommen. De
|
||||||
|
versiehistorie is de changelog van MijnBesluit, dus er is geen tweede lijst: de view
|
||||||
|
`serverchangelog` (script `db\handmatig\22c_serverchangelog_view.sql`, als root) geeft
|
||||||
|
`version_history` in dat formaat terug. `Product` is vast "MijnBesluit", `Soort` vast "Nieuw" (de
|
||||||
|
lijst bevat geen bugfixes), `Actief` is `zichtbaar` en `Updatedatum` is `gewijzigd_op`, dat de site
|
||||||
|
bij elke wijziging opnieuw zet. De agent leest de view alleen.
|
||||||
|
|
||||||
|
Instellen in SideKick bij de agent van MijnBesluit: "Changelog: tabel" = `serverchangelog`, `DbNaam` =
|
||||||
|
`mijnbesluit`, en een gebruiker met SELECT op de view (het script geeft dat aan `mijnbesluit_app`;
|
||||||
|
een aparte leesgebruiker kan ook, het voorbeeld staat in het script). Het wachtwoord alleen op de
|
||||||
|
server invullen. Voorwaarden: de agent kent de actie `ChangelogLezen` en het project staat op Weg hub.
|
||||||
|
De inhoud gaat naar het SideKick-scherm van de beheerder: geen persoonsgegevens in een regel.
|
||||||
|
|
||||||
## Oudere apps
|
## Oudere apps
|
||||||
|
|
||||||
De tekst van de versiehistorie zat tot en met appversie 1.1 (versiecode 3) in de app zelf. Die
|
De tekst van de versiehistorie zat tot en met appversie 1.1 (versiecode 3) in de app zelf. Die
|
||||||
|
|||||||
@@ -0,0 +1,81 @@
|
|||||||
|
APP STORE-VERMELDING - MijnBesluit (nl.mijnbesluit.app)
|
||||||
|
Taal: Nederlands (Nederland). Concept; in App Store Connect onder Distribution > de versie.
|
||||||
|
Afgeleid van docs\playstore\winkelvermelding.txt, met de limieten van Apple.
|
||||||
|
|
||||||
|
NAAM (max 30) - nu 11
|
||||||
|
MijnBesluit
|
||||||
|
|
||||||
|
ONDERTITEL (max 30) - nu 30
|
||||||
|
Wachtwoorden en wensen bewaard
|
||||||
|
|
||||||
|
PROMOTIETEKST (max 170, mag altijd worden aangepast zonder nieuwe versie) - nu 145
|
||||||
|
Wachtwoorden, wensen en berichten veilig bewaard, en pas vrij te geven wanneer jij dat wilt. Inloggen zonder wachtwoord, met een code per e-mail.
|
||||||
|
|
||||||
|
TREFWOORDEN (max 100, komma's zonder spaties; woorden uit de naam en ondertitel niet herhalen) - nu 99
|
||||||
|
kluis,wachtwoorden,nalatenschap,uitvaart,testament,berichten,familie,polissen,overlijden,documenten
|
||||||
|
|
||||||
|
BESCHRIJVING (max 4000) - nu 2689
|
||||||
|
Er zit van alles in je hoofd waar niemand anders bij kan. Het wachtwoord van je e-mail, waar de polissen liggen, hoe je je uitvaart voor je ziet, en de dingen die je nog tegen iemand had willen zeggen. Zolang alles goed gaat maakt dat niet uit. Gaat het niet goed, dan staan de mensen die van je houden met lege handen, op het slechtst denkbare moment.
|
||||||
|
|
||||||
|
MijnBesluit is de plek waar dat wél ergens staat. Veilig opgeborgen zolang het van jou alleen is, en beschikbaar op het moment dat jij hebt bepaald.
|
||||||
|
|
||||||
|
WAT KUN JE ERMEE?
|
||||||
|
|
||||||
|
Je kluis
|
||||||
|
Bewaar wachtwoorden voor websites en apparaten, de pincode van je telefoon, je sociale media, polissen, documenten en je uitvaartwensen. Alles wordt versleuteld opgeslagen. Per onderdeel kies je zelf of het alleen voor jou is, pas na je overlijden vrijkomt, of op een datum die jij bepaalt.
|
||||||
|
|
||||||
|
Je mensen
|
||||||
|
Maak profielen aan voor de familie en vrienden die erbij horen en nodig ze uit. Zodra iemand de uitnodiging accepteert, is het profiel van hem of haar en vult die persoon zijn of haar eigen MijnBesluit in.
|
||||||
|
|
||||||
|
Momenten
|
||||||
|
Zet een bericht of video klaar voor iemand, om te bezorgen op een datum die jij kiest, of pas wanneer jij er niet meer bent. Een achttiende verjaardag, een huwelijk, of gewoon iets wat je nog wilde zeggen. Je kunt ook berichten ontvangen die een ander voor jou heeft klaargezet.
|
||||||
|
|
||||||
|
Je nalatenschap
|
||||||
|
Bepaal wie straks mag regelen wat er geregeld moet worden. Die persoon krijgt niet zomaar toegang: meerdere mensen moeten onafhankelijk van elkaar melden dat je bent overleden, waaronder iemand die jij zelf hebt aangewezen. Daarna heb jij nog een termijn om te laten weten dat je er gewoon nog bent. Pas als die voorbij is, gaat het open.
|
||||||
|
|
||||||
|
HOE VEILIG IS HET?
|
||||||
|
|
||||||
|
- Alles wordt versleuteld opgeslagen, ook bijlagen zoals een foto van je polis.
|
||||||
|
- Je hebt geen wachtwoord nodig: je logt in met je e-mailadres en een code die een kwartier geldig is en maar één keer werkt. Er is dus niets te onthouden, te raden of te hergebruiken.
|
||||||
|
- Wat je op "alleen voor mijzelf" zet, blijft dat. Je nalatenschapsbeheerder krijgt alleen wat jij hebt vrijgegeven, en pas op het moment dat jij hebt bepaald.
|
||||||
|
- Er wordt bijgehouden wie wat opent, wijzigt of vrijgeeft.
|
||||||
|
|
||||||
|
Volledige zekerheid bestaat niet, en dat beweren we ook niet. Wat we wel doen is elke keuze zo maken dat een fout of een inbraak zo min mogelijk oplevert.
|
||||||
|
|
||||||
|
JOUW GEGEVENS, JOUW KEUZE
|
||||||
|
|
||||||
|
Een eigen MijnBesluit is gratis. Je kunt alles wat je hebt ingevoerd altijd zelf wijzigen of verwijderen, en je kunt je account en bijbehorende gegevens in de app of via mijnbesluit.nl laten verwijderen.
|
||||||
|
|
||||||
|
MijnBesluit is een product van Kwik-Bit Automatisering B.V.
|
||||||
|
Privacyverklaring: https://mijnbesluit.nl/privacy
|
||||||
|
|
||||||
|
WAT IS ER NIEUW (versie 1.1) - nu 117
|
||||||
|
Eerste versie voor iOS: je kluis, je mensen, momenten en je nalatenschap, net als in de Android-app en op de website.
|
||||||
|
|
||||||
|
ADRESSEN
|
||||||
|
Ondersteunings-URL: https://mijnbesluit.nl/ (moet een pagina zijn waar iemand hulp of contact vindt; nog te kiezen)
|
||||||
|
Marketing-URL: https://mijnbesluit.nl/ (mag leeg)
|
||||||
|
Privacybeleid-URL: https://mijnbesluit.nl/privacy
|
||||||
|
Copyright: 2026 Kwik-Bit Automatisering B.V.
|
||||||
|
|
||||||
|
CATEGORIE
|
||||||
|
Primair: Productiviteit (zoals in Google Play). Secundair: Levensstijl.
|
||||||
|
|
||||||
|
SCHERMAFBEELDINGEN (nog te maken, niet te hergebruiken van Android: 1180 x 2276 is geen toegestane maat)
|
||||||
|
- iPhone 6,9 inch: 1320 x 2868 (of 1290 x 2796); verplicht. De kleinere iPhone-maten schaalt App Store Connect hieruit af.
|
||||||
|
- iPad 13 inch: 2064 x 2752; verplicht zolang de app op iPad draait (Info.plist: UIDeviceFamily 1 en 2).
|
||||||
|
- 1 tot 10 per soort; zelfde vijf schermen als bij Google: overzicht, mensen, kluis, momenten, nalatenschap.
|
||||||
|
|
||||||
|
TOEGANG VOOR DE REVIEWER (App Review Information)
|
||||||
|
Inloggen gaat met e-mailadres en een code die per e-mail komt. Reviewaccount review@mijnbesluit.nl; de code komt in de reviewmailbox:
|
||||||
|
webmail https://oma.kwik-bit.nl:8443/webmail/ (zie MijnBesluit-playstore.md). Zet die uitleg en het mailboxwachtwoord in de velden van App Review.
|
||||||
|
Wachtwoord van de reviewmailbox pas na de review wijzigen.
|
||||||
|
|
||||||
|
LEEFTIJDSCLASSIFICATIE (door Jonathan te beantwoorden)
|
||||||
|
Doelgroep 18+ en "Contains ads" zoals bij Google; de vragenlijst van Apple over o.a. user-generated content, advertenties en
|
||||||
|
communicatie tussen gebruikers loopt anders dan bij Google, dus de antwoorden zelf doornemen.
|
||||||
|
|
||||||
|
APP PRIVACY (door Jonathan te beantwoorden; voorstel, controleren)
|
||||||
|
Verzameld en gekoppeld aan de gebruiker, niet voor tracking: e-mailadres, naam (profiel), foto's of video's (alleen wat de gebruiker zelf kiest),
|
||||||
|
gebruikersinhoud (kluisitems, berichten), gebruiks- en logboekgegevens voor beveiliging. Tracking: nee (geen advertentie-SDK; de advertentie komt van de
|
||||||
|
eigen server). Verwijderen van account en gegevens kan in de app en via mijnbesluit.nl.
|
||||||
|
After Width: | Height: | Size: 263 KiB |
|
After Width: | Height: | Size: 236 KiB |
|
After Width: | Height: | Size: 182 KiB |
|
After Width: | Height: | Size: 210 KiB |
|
After Width: | Height: | Size: 446 KiB |
|
After Width: | Height: | Size: 266 KiB |
|
After Width: | Height: | Size: 239 KiB |
|
After Width: | Height: | Size: 211 KiB |
|
After Width: | Height: | Size: 235 KiB |
|
After Width: | Height: | Size: 310 KiB |
@@ -41,6 +41,28 @@ gecompileerd is, geeft meestal een handvol kleine dingen (een ontbrekende using,
|
|||||||
API-naam). `PrivacyInfo.xcprivacy` moet na het bouwen in de `.app` zitten
|
API-naam). `PrivacyInfo.xcprivacy` moet na het bouwen in de `.app` zitten
|
||||||
(`find . -name PrivacyInfo.xcprivacy`).
|
(`find . -name PrivacyInfo.xcprivacy`).
|
||||||
|
|
||||||
|
## Een gehuurde Mac (MacinCloud) zonder beheerrechten
|
||||||
|
|
||||||
|
Beproefd op 20 oktober 2026: een Managed Server van MacinCloud (Mac mini M2, macOS Tahoe 26.x,
|
||||||
|
Xcode 26.6), met Windows via RDP. Daar heb je geen beheerrechten, en de .NET die erop staat heeft
|
||||||
|
een kapotte werkbelastinginstelling. Wat wel werkt:
|
||||||
|
|
||||||
|
1. Een eigen .NET in je eigen map, in dezelfde versie als de systeem-.NET:
|
||||||
|
`bash ~/dotnet-install.sh --version 10.0.302 --install-dir ~/dotnet302`
|
||||||
|
(script van https://dot.net/v1/dotnet-install.sh), en in elk Terminal-venster:
|
||||||
|
`export DOTNET_ROOT=$HOME/dotnet302 PATH=$HOME/dotnet302:$PATH`.
|
||||||
|
2. `dotnet workload install maui-ios` daarmee. **Let op de Xcode-versie:** de nieuwste .NET
|
||||||
|
(10.0.4xx) levert iOS 27.1 en eist Xcode 27.1 ("requires Xcode 27.1"). De 10.0.3xx-band levert
|
||||||
|
iOS 26.5 en past bij Xcode 26.x. Gebruik dus de oudere SDK zolang Xcode 26 de nieuwste stabiele is.
|
||||||
|
3. `tools/ios-bouwen.sh simulator`. Het script zet `MetAndroid=false` (anders eist de build de
|
||||||
|
Android-werkbelasting) en `CodesignKey=-` (lokale handtekening; de simulator wil geen
|
||||||
|
certificaat, maar de Keychain-rechten in `Entitlements.plist` vragen om een handtekening).
|
||||||
|
Geef nooit `-p:TargetFrameworks=...` op de opdrachtregel mee: dat gaat ook naar de gedeelde
|
||||||
|
projecten (net8.0) en breekt hun herstel.
|
||||||
|
|
||||||
|
Resultaat: de app bouwt en draait in de iPhone 16 Pro-simulator (iOS 18.0), inclusief inloggen en
|
||||||
|
de overzichtsschermen.
|
||||||
|
|
||||||
## Wat er nog moet
|
## Wat er nog moet
|
||||||
|
|
||||||
**Een Mac.** Apple's bouwgereedschap draait alleen op macOS. Visual Studio op Windows verbindt
|
**Een Mac.** Apple's bouwgereedschap draait alleen op macOS. Visual Studio op Windows verbindt
|
||||||
@@ -58,6 +80,42 @@ meldingen, en verder werkt alles. Dat komt erbij zodra het account er is, want h
|
|||||||
vast: een APNs-sleutel, de Push Notifications-capability, een provisioning profile en een
|
vast: een APNs-sleutel, de Push Notifications-capability, een provisioning profile en een
|
||||||
`Entitlements.plist`.
|
`Entitlements.plist`.
|
||||||
|
|
||||||
|
## Een .ipa voor TestFlight en de App Store
|
||||||
|
|
||||||
|
Kan zodra het Apple Developer Program actief is (Kwik-Bit Automatisering B.V., aangemeld op
|
||||||
|
27 oktober 2026, betaald; de accountpagina toont geen "Pending" meer). Stappen, in deze volgorde:
|
||||||
|
|
||||||
|
1. **Voorwaarden accepteren** op developer.apple.com/account (de nieuwe licentieovereenkomst,
|
||||||
|
uiterlijk 14 november 2026). Alleen de accounthouder kan dat. Zonder dit werken Certificates,
|
||||||
|
App Store Connect en de API niet meer.
|
||||||
|
2. **App-ID registreren**: Certificates, IDs & Profiles > Identifiers > App IDs, bundel-ID
|
||||||
|
`nl.mijnbesluit.app` (gelijk aan `ApplicationId`). Capabilities: voorlopig geen extra;
|
||||||
|
Push Notifications komt later met APNs.
|
||||||
|
3. **Distributiecertificaat** (Apple Distribution) maken: Xcode > Settings > Accounts > het team >
|
||||||
|
Manage Certificates > + > Apple Distribution, op de Mac waar je bouwt. De private sleutel
|
||||||
|
blijft in de sleutelhanger van die Mac; bij een gehuurde Mac is die weg als de Mac weg is,
|
||||||
|
dus bewaar er een export van (met wachtwoord, buiten git).
|
||||||
|
4. **App Store-profiel** (Provisioning Profiles > + > App Store Connect) voor `nl.mijnbesluit.app`
|
||||||
|
met dat certificaat; downloaden en dubbelklikken op de Mac. Noteer de naam van het profiel.
|
||||||
|
5. **App-record** in App Store Connect (Apps > + > Nieuwe app): naam MijnBesluit, bundel-ID
|
||||||
|
`nl.mijnbesluit.app`, primaire taal Nederlands, SKU naar keuze.
|
||||||
|
6. **Bouwen**: op de Mac, met de namen uit stap 3 en 4:
|
||||||
|
|
||||||
|
```
|
||||||
|
export IOS_SIGN_KEY="Apple Distribution: Kwik-Bit Automatisering B.V. (TEAMID)"
|
||||||
|
export IOS_PROFILE="<naam van het profiel>"
|
||||||
|
tools/ios-bouwen.sh archief
|
||||||
|
```
|
||||||
|
|
||||||
|
Het script toont eerst welke certificaten en profielen er staan, maakt dan de `.ipa`
|
||||||
|
(`dotnet publish`, ios-arm64, Release, `ArchiveOnBuild`) en zegt waar hij staat. Het bouwnummer
|
||||||
|
(`ApplicationVersion`) moet bij elke upload hoger zijn dan de vorige.
|
||||||
|
7. **Uploaden** met de app Transporter (Mac App Store, gratis): inloggen met het Apple-account,
|
||||||
|
`.ipa` erin slepen. Na enkele minuten staat de build onder TestFlight; de exportverklaring en
|
||||||
|
de App Privacy-antwoorden volgen in App Store Connect (zie hieronder).
|
||||||
|
|
||||||
|
Niet gedaan en niet getest: dit script en deze stappen zijn nog nooit tegen Apple gedraaid.
|
||||||
|
|
||||||
## Twee vallen waar je vroeg in loopt
|
## Twee vallen waar je vroeg in loopt
|
||||||
|
|
||||||
**Ontbrekende gebruikstoelichting = de app sluit.** Vraag je om Face ID zonder
|
**Ontbrekende gebruikstoelichting = de app sluit.** Vraag je om Face ID zonder
|
||||||
|
|||||||
@@ -16,7 +16,10 @@
|
|||||||
|
|
||||||
of blijvend, door MetIos in een Directory.Build.props of in de omgeving te zetten op de
|
of blijvend, door MetIos in een Directory.Build.props of in de omgeving te zetten op de
|
||||||
machine die wél aan een Mac hangt. Zie docs/iOS-app.md. -->
|
machine die wél aan een Mac hangt. Zie docs/iOS-app.md. -->
|
||||||
<TargetFrameworks>net10.0-android</TargetFrameworks>
|
<!-- MetAndroid=false laat Android weg: op een Mac die alleen iOS bouwt staat de Android-
|
||||||
|
werkbelasting er niet, en dan weigert de hele build (NETSDK1147). Zonder deze schakelaar
|
||||||
|
verandert er niets: Android blijft standaard aan. -->
|
||||||
|
<TargetFrameworks Condition="'$(MetAndroid)' != 'false'">net10.0-android</TargetFrameworks>
|
||||||
<TargetFrameworks Condition="'$(MetIos)' == 'true'">$(TargetFrameworks);net10.0-ios</TargetFrameworks>
|
<TargetFrameworks Condition="'$(MetIos)' == 'true'">$(TargetFrameworks);net10.0-ios</TargetFrameworks>
|
||||||
|
|
||||||
<OutputType>Exe</OutputType>
|
<OutputType>Exe</OutputType>
|
||||||
@@ -29,7 +32,7 @@
|
|||||||
<ApplicationTitle>MijnBesluit</ApplicationTitle>
|
<ApplicationTitle>MijnBesluit</ApplicationTitle>
|
||||||
<ApplicationId>nl.mijnbesluit.app</ApplicationId>
|
<ApplicationId>nl.mijnbesluit.app</ApplicationId>
|
||||||
<ApplicationDisplayVersion>1.1</ApplicationDisplayVersion>
|
<ApplicationDisplayVersion>1.1</ApplicationDisplayVersion>
|
||||||
<ApplicationVersion>3</ApplicationVersion>
|
<ApplicationVersion>4</ApplicationVersion>
|
||||||
|
|
||||||
<SupportedOSPlatformVersion Condition="$([MSBuild]::GetTargetPlatformIdentifier('$(TargetFramework)')) == 'android'">24.0</SupportedOSPlatformVersion>
|
<SupportedOSPlatformVersion Condition="$([MSBuild]::GetTargetPlatformIdentifier('$(TargetFramework)')) == 'android'">24.0</SupportedOSPlatformVersion>
|
||||||
|
|
||||||
|
|||||||
@@ -7,10 +7,10 @@
|
|||||||
|
|
||||||
<key>UIDeviceFamily</key>
|
<key>UIDeviceFamily</key>
|
||||||
<array>
|
<array>
|
||||||
<!-- 1 = iPhone, 2 = iPad. De schermen zijn gemaakt voor een telefoon, maar een iPad
|
<!-- 1 = iPhone, 2 = iPad. Voorlopig alleen iPhone: de indeling op een iPad (simulator, 1 nov 2026)
|
||||||
weigeren levert alleen een gebruiker op die het niet snapt. -->
|
toont een lege pagina met de onderbalk midden in beeld. Waarde 2 er pas weer bij als de
|
||||||
|
iPad-weergave klopt en er iPad-schermafbeeldingen zijn. -->
|
||||||
<integer>1</integer>
|
<integer>1</integer>
|
||||||
<integer>2</integer>
|
|
||||||
</array>
|
</array>
|
||||||
|
|
||||||
<key>UIRequiredDeviceCapabilities</key>
|
<key>UIRequiredDeviceCapabilities</key>
|
||||||
|
|||||||
@@ -0,0 +1,48 @@
|
|||||||
|
@inject IConfiguration Instellingen
|
||||||
|
@inject IWebHostEnvironment Omgeving
|
||||||
|
|
||||||
|
@* De "Download in de App Store" en "Ontdek het op Google Play"-knoppen.
|
||||||
|
|
||||||
|
Elke knop verschijnt pas als er twee dingen zijn: een adres in appsettings.json (Apps:AppStoreUrl
|
||||||
|
en Apps:GooglePlayUrl, leeg zolang de app daar niet staat) en het officiële plaatje van de winkel
|
||||||
|
in wwwroot/badges. De plaatjes tekenen we niet zelf na: Apple en Google leveren ze kant-en-klaar
|
||||||
|
aan en eisen dat ze onaangepast worden gebruikt. Zie wwwroot/badges/LEESMIJ.txt. *@
|
||||||
|
|
||||||
|
@if (_knoppen.Count > 0)
|
||||||
|
{
|
||||||
|
<div class="appknoppen">
|
||||||
|
@foreach (var knop in _knoppen)
|
||||||
|
{
|
||||||
|
<a href="@knop.Adres" target="_blank" rel="noopener" title="@knop.Titel">
|
||||||
|
<img src="@knop.Plaatje" alt="@knop.Titel" />
|
||||||
|
</a>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
|
||||||
|
@code {
|
||||||
|
private readonly List<(string Adres, string Plaatje, string Titel)> _knoppen = new();
|
||||||
|
|
||||||
|
protected override void OnInitialized()
|
||||||
|
{
|
||||||
|
Voeg("Apps:AppStoreUrl", "app-store", "Download in de App Store");
|
||||||
|
Voeg("Apps:GooglePlayUrl", "google-play", "Ontdek het op Google Play");
|
||||||
|
}
|
||||||
|
|
||||||
|
private void Voeg(string instelling, string bestandsnaam, string titel)
|
||||||
|
{
|
||||||
|
var adres = Instellingen[instelling];
|
||||||
|
if (string.IsNullOrWhiteSpace(adres)) return;
|
||||||
|
|
||||||
|
// Het plaatje mag .svg of .png zijn, wat je van de winkel hebt gekregen.
|
||||||
|
foreach (var extensie in new[] { "svg", "png" })
|
||||||
|
{
|
||||||
|
var pad = $"badges/{bestandsnaam}.{extensie}";
|
||||||
|
if (Omgeving.WebRootFileProvider.GetFileInfo(pad).Exists)
|
||||||
|
{
|
||||||
|
_knoppen.Add((adres, "/" + pad, titel));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -216,6 +216,9 @@
|
|||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
@* De knoppen voor de app in de winkels; zie AppDownload.razor. *@
|
||||||
|
<AppDownload />
|
||||||
</Authorized>
|
</Authorized>
|
||||||
</AuthorizeView>
|
</AuthorizeView>
|
||||||
|
|
||||||
|
|||||||
@@ -93,6 +93,8 @@
|
|||||||
<p class="zacht"><a href="/wat-is-mijnbesluit">Wat is MijnBesluit?</a></p>
|
<p class="zacht"><a href="/wat-is-mijnbesluit">Wat is MijnBesluit?</a></p>
|
||||||
<p class="zacht"><a href="/registreren">Ik ben nieuw en wil een account aanmaken</a></p>
|
<p class="zacht"><a href="/registreren">Ik ben nieuw en wil een account aanmaken</a></p>
|
||||||
<p class="zacht"><a href="/adverteren">Ik wil adverteren op MijnBesluit</a></p>
|
<p class="zacht"><a href="/adverteren">Ik wil adverteren op MijnBesluit</a></p>
|
||||||
|
|
||||||
|
<AppDownload />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@code {
|
@code {
|
||||||
|
|||||||
@@ -15,5 +15,7 @@
|
|||||||
<a class="knop rustig" href="/inloggen">Inloggen</a>
|
<a class="knop rustig" href="/inloggen">Inloggen</a>
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
|
<AppDownload />
|
||||||
|
|
||||||
<p class="voet"><a href="/inloggen">Terug naar inloggen</a></p>
|
<p class="voet"><a href="/inloggen">Terug naar inloggen</a></p>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -11,6 +11,10 @@
|
|||||||
"Site": {
|
"Site": {
|
||||||
"Url": "https://localhost:7014"
|
"Url": "https://localhost:7014"
|
||||||
},
|
},
|
||||||
|
"Apps": {
|
||||||
|
"GooglePlayUrl": "https://play.google.com/store/apps/details?id=nl.mijnbesluit.app",
|
||||||
|
"AppStoreUrl": "https://apps.apple.com/nl/app/id6828724812"
|
||||||
|
},
|
||||||
"Smtp": {
|
"Smtp": {
|
||||||
"Host": "localhost",
|
"Host": "localhost",
|
||||||
"Port": 587,
|
"Port": 587,
|
||||||
|
|||||||
@@ -575,3 +575,14 @@ hr { border: none; border-top: 1px solid var(--rand); margin: 1.25rem 0; }
|
|||||||
|
|
||||||
.voet { text-align: center; margin-top: 2rem; font-size: .9rem; }
|
.voet { text-align: center; margin-top: 2rem; font-size: .9rem; }
|
||||||
.voet a { color: var(--inkt-zacht); }
|
.voet a { color: var(--inkt-zacht); }
|
||||||
|
|
||||||
|
/* Download-knoppen van de winkels (AppDownload.razor). De plaatjes zelf blijven onaangepast;
|
||||||
|
we bepalen alleen de hoogte en de ruimte eromheen. */
|
||||||
|
.appknoppen { display: flex; flex-wrap: wrap; align-items: center; gap: .75rem; margin-top: 1.25rem; }
|
||||||
|
.appknoppen a { display: inline-block; line-height: 0; }
|
||||||
|
.appknoppen img { height: 44px; width: auto; display: block; }
|
||||||
|
/* Het PNG van Google heeft een brede doorzichtige rand rondom de knop zelf (de knop is maar 241 van
|
||||||
|
de 572 pixels hoog). Daarom hier een grotere hoogte en een negatieve marge, zodat de zichtbare knop
|
||||||
|
net zo hoog wordt als die van Apple. Het plaatje zelf blijft onaangepast. Is het een bijgesneden
|
||||||
|
.svg, dan is dit niet nodig en geldt gewoon de 44px hierboven. */
|
||||||
|
.appknoppen img[src$="google-play.png"] { height: 104px; margin: -30px -6px; }
|
||||||
|
|||||||
@@ -0,0 +1,11 @@
|
|||||||
|
Hier horen de officiele knoppen van de winkels:
|
||||||
|
|
||||||
|
app-store.svg (of .png) "Download in de App Store" - Apple
|
||||||
|
google-play.svg (of .png) "Ontdek het op Google Play" - Google
|
||||||
|
|
||||||
|
Haal ze zelf op bij de winkels en pas ze niet aan (kleur, vorm, tekst):
|
||||||
|
Apple: developer.apple.com/app-store/marketing/guidelines (sectie "Download on the App Store", Nederlandse versie)
|
||||||
|
Google: play.google.com/intl/nl/badges (Nederlandse knop)
|
||||||
|
|
||||||
|
Een knop verschijnt op de inlogpagina pas als zijn bestand hier staat EN het adres in
|
||||||
|
appsettings.json is ingevuld (Apps:AppStoreUrl, Apps:GooglePlayUrl).
|
||||||
|
After Width: | Height: | Size: 9.6 KiB |
|
After Width: | Height: | Size: 31 KiB |
@@ -0,0 +1,38 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""iPhone-schermafbeeldingen klaarmaken voor de App Store.
|
||||||
|
|
||||||
|
Per bestand in de invoermap:
|
||||||
|
- de statusbalk bovenin (tijd, 5G, batterij, opname- en microfoonindicator) wordt overgeschilderd met
|
||||||
|
de kleur van de kop van de app, zodat er geen tijd, netwerk of toestelinfo in staat;
|
||||||
|
- het plaatje wordt op 1290 x 2796 gebracht (iPhone 6,9 inch, toegestaan door de App Store).
|
||||||
|
Alleen de statusbalk wordt aangepast; de rest van het scherm blijft zoals het is.
|
||||||
|
|
||||||
|
Gebruik: python tools/appstore-schermen.py <invoermap> <uitvoermap>
|
||||||
|
Invoer: ORIGINELE iPhone-schermafbeeldingen (1179 x 2556 of 1290 x 2796, dezelfde beeldverhouding).
|
||||||
|
Een verkleind plaatje (bijvoorbeeld uit een chat) wordt onscherp zodra het naar 1290 x 2796 groeit.
|
||||||
|
Nodig: Pillow (pip install pillow).
|
||||||
|
"""
|
||||||
|
import sys, pathlib
|
||||||
|
from PIL import Image
|
||||||
|
|
||||||
|
DOEL = (1290, 2796)
|
||||||
|
STATUSBALK = 0.055 # aandeel van de hoogte dat de statusbalk beslaat (iPhone met Dynamic Island)
|
||||||
|
|
||||||
|
def verwerk(pad, uit):
|
||||||
|
im = Image.open(pad).convert("RGB")
|
||||||
|
b, h = im.size
|
||||||
|
if abs(b / h - DOEL[0] / DOEL[1]) > 0.01:
|
||||||
|
raise SystemExit(f"{pad.name}: beeldverhouding {b}x{h} past niet bij een iPhone-schermafbeelding")
|
||||||
|
rand = int(h * STATUSBALK)
|
||||||
|
kleur = im.getpixel((b // 2, rand + 4))
|
||||||
|
im.paste(kleur, (0, 0, b, rand))
|
||||||
|
im = im.resize(DOEL, Image.LANCZOS)
|
||||||
|
im.save(uit / (pad.stem + ".png"), optimize=True)
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
inv, uit = pathlib.Path(sys.argv[1]), pathlib.Path(sys.argv[2])
|
||||||
|
uit.mkdir(parents=True, exist_ok=True)
|
||||||
|
for f in sorted(inv.iterdir()):
|
||||||
|
if f.suffix.lower() in (".png", ".jpg", ".jpeg"):
|
||||||
|
verwerk(f, uit)
|
||||||
|
print("klaar:", f.name)
|
||||||
@@ -3,7 +3,12 @@
|
|||||||
#
|
#
|
||||||
# tools/ios-bouwen.sh controle alleen kijken of de Mac er klaar voor is
|
# tools/ios-bouwen.sh controle alleen kijken of de Mac er klaar voor is
|
||||||
# tools/ios-bouwen.sh simulator bouwen en starten in de iPhone-simulator (geen account nodig)
|
# tools/ios-bouwen.sh simulator bouwen en starten in de iPhone-simulator (geen account nodig)
|
||||||
|
# tools/ios-bouwen.sh ipad zoals 'simulator', maar in de iPad-simulator (voor App Store-schermen);
|
||||||
|
# andere simulator: IOS_SIMULATOR="iPad Pro 13-inch (M4)" tools/ios-bouwen.sh simulator
|
||||||
# tools/ios-bouwen.sh toestel bouwen en op een aangesloten iPhone zetten (Apple-ID / team nodig)
|
# tools/ios-bouwen.sh toestel bouwen en op een aangesloten iPhone zetten (Apple-ID / team nodig)
|
||||||
|
# tools/ios-bouwen.sh archief een .ipa voor App Store Connect / TestFlight maken
|
||||||
|
# (betaald Apple Developer Program, distributiecertificaat en
|
||||||
|
# App Store-profiel nodig; zie docs/iOS-app.md)
|
||||||
#
|
#
|
||||||
# Eerste keer: chmod +x tools/ios-bouwen.sh
|
# Eerste keer: chmod +x tools/ios-bouwen.sh
|
||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
@@ -40,13 +45,31 @@ case "$MODUS" in
|
|||||||
controle)
|
controle)
|
||||||
controle
|
controle
|
||||||
;;
|
;;
|
||||||
simulator)
|
simulator|ipad)
|
||||||
controle
|
controle
|
||||||
|
[[ "$MODUS" == "ipad" && -z "${IOS_SIMULATOR:-}" ]] && IOS_SIMULATOR="iPad Pro 13-inch (M4)"
|
||||||
stap "Bouwen en starten in de simulator"
|
stap "Bouwen en starten in de simulator"
|
||||||
# Eerst de lijst met simulators tonen, zodat je ziet welke je kunt kiezen.
|
# Eerst de lijst met simulators tonen, zodat je ziet welke je kunt kiezen.
|
||||||
xcrun simctl list devices available | grep -i iphone | head -5 || true
|
xcrun simctl list devices available | grep -iE 'iphone|ipad' | head -12 || true
|
||||||
dotnet build "$PROJECT" -f "$TFM" -p:MetIos=true -t:Run \
|
APPARAAT=()
|
||||||
-p:RuntimeIdentifier=iossimulator-arm64
|
if [[ -n "${IOS_SIMULATOR:-}" ]]; then
|
||||||
|
UDID="$(xcrun simctl list devices available | grep -F " ${IOS_SIMULATOR} (" | head -1 | sed -E 's/.*\(([0-9A-F-]{36})\).*/\1/')"
|
||||||
|
[[ -n "$UDID" ]] || stop "simulator '${IOS_SIMULATOR}' niet gevonden; kies een naam uit de lijst hierboven (Xcode > Settings > Components voor extra simulators)."
|
||||||
|
echo "Simulator: ${IOS_SIMULATOR} ($UDID)"
|
||||||
|
xcrun simctl boot "$UDID" 2>/dev/null || true
|
||||||
|
open -a Simulator
|
||||||
|
APPARAAT=(-p:_DeviceName=":v2:udid=$UDID")
|
||||||
|
fi
|
||||||
|
# Eerst bouwen, daarna starten: met alleen -t:Run bouwt hij de app niet en weet hij niet wat
|
||||||
|
# hij moet starten. MetAndroid=false: op de Mac staat alleen de iOS-werkbelasting.
|
||||||
|
# CodesignKey=-: een eenvoudige lokale handtekening. De simulator wil geen certificaat, maar de
|
||||||
|
# app heeft wel een handtekening nodig vanwege de Keychain-rechten in Entitlements.plist.
|
||||||
|
dotnet build "$PROJECT" -f "$TFM" -p:MetIos=true -p:MetAndroid=false \
|
||||||
|
-p:RuntimeIdentifier=iossimulator-arm64 -p:CodesignKey=-
|
||||||
|
dotnet build "$PROJECT" -f "$TFM" -p:MetIos=true -p:MetAndroid=false -t:Run \
|
||||||
|
-p:RuntimeIdentifier=iossimulator-arm64 -p:CodesignKey=- ${APPARAAT[@]+"${APPARAAT[@]}"}
|
||||||
|
echo
|
||||||
|
echo "Schermafdruk (zonder statusbalk-ruis, op ware grootte): xcrun simctl io booted screenshot ~/Desktop/ipad-1.png"
|
||||||
;;
|
;;
|
||||||
toestel)
|
toestel)
|
||||||
controle
|
controle
|
||||||
@@ -58,13 +81,37 @@ case "$MODUS" in
|
|||||||
echo "(Instellingen > Privacy en beveiliging > Ontwikkelaarsmodus)."
|
echo "(Instellingen > Privacy en beveiliging > Ontwikkelaarsmodus)."
|
||||||
echo "Zonder betaald account werkt dit 7 dagen per installatie, en zonder meldingen."
|
echo "Zonder betaald account werkt dit 7 dagen per installatie, en zonder meldingen."
|
||||||
stap "Bouwen voor het toestel"
|
stap "Bouwen voor het toestel"
|
||||||
dotnet build "$PROJECT" -f "$TFM" -p:MetIos=true \
|
dotnet build "$PROJECT" -f "$TFM" -p:MetIos=true -p:MetAndroid=false \
|
||||||
-p:RuntimeIdentifier=ios-arm64 -p:CodesignKey="Apple Development"
|
-p:RuntimeIdentifier=ios-arm64 -p:CodesignKey="Apple Development"
|
||||||
echo
|
echo
|
||||||
echo "Gebouwd. Installeren gaat het makkelijkst via Visual Studio (Pair to Mac) of"
|
echo "Gebouwd. Installeren gaat het makkelijkst via Visual Studio (Pair to Mac) of"
|
||||||
echo "Xcode > Window > Devices and Simulators, of: dotnet build -t:Run -p:_DeviceName=<UDID>"
|
echo "Xcode > Window > Devices and Simulators, of: dotnet build -t:Run -p:_DeviceName=<UDID>"
|
||||||
;;
|
;;
|
||||||
|
archief)
|
||||||
|
controle
|
||||||
|
# Namen, geen geheimen: de exacte naam van het distributiecertificaat en van het App Store-profiel.
|
||||||
|
# export IOS_SIGN_KEY="Apple Distribution: Kwik-Bit Automatisering B.V. (TEAMID)"
|
||||||
|
# export IOS_PROFILE="MijnBesluit App Store"
|
||||||
|
# Zie de lijst hieronder voor wat er op deze Mac staat.
|
||||||
|
stap "Distributiecertificaten op deze Mac"
|
||||||
|
security find-identity -v -p codesigning | grep -i 'Apple Distribution' \
|
||||||
|
|| echo "(geen 'Apple Distribution'-certificaat gevonden; maak er een in Xcode > Settings > Accounts > Manage Certificates)"
|
||||||
|
stap "Provisioning profiles op deze Mac"
|
||||||
|
ls "$HOME/Library/MobileDevice/Provisioning Profiles" 2>/dev/null || echo "(geen profielen gevonden)"
|
||||||
|
[[ -n "${IOS_SIGN_KEY:-}" ]] || stop "zet IOS_SIGN_KEY op de exacte naam van het 'Apple Distribution'-certificaat (zie de lijst hierboven)."
|
||||||
|
[[ -n "${IOS_PROFILE:-}" ]] || stop "zet IOS_PROFILE op de naam van het App Store-profiel voor nl.mijnbesluit.app."
|
||||||
|
stap "Archief maken (ios-arm64, Release)"
|
||||||
|
# Let op: ApplicationVersion (bouwnummer) moet bij elke upload hoger zijn dan de vorige.
|
||||||
|
dotnet publish "$PROJECT" -f "$TFM" -c Release -p:MetIos=true -p:MetAndroid=false \
|
||||||
|
-p:RuntimeIdentifier=ios-arm64 -p:ArchiveOnBuild=true \
|
||||||
|
-p:CodesignKey="$IOS_SIGN_KEY" -p:CodesignProvision="$IOS_PROFILE"
|
||||||
|
echo
|
||||||
|
echo "Klaar. De .ipa staat onder:"
|
||||||
|
find "$(dirname "$PROJECT")/bin/Release" -name '*.ipa' -print 2>/dev/null || true
|
||||||
|
echo "Uploaden: open de app Transporter (gratis, Mac App Store), log in met het Apple-account en"
|
||||||
|
echo "sleep de .ipa erin. Daarna staat de build na enkele minuten in App Store Connect > TestFlight."
|
||||||
|
;;
|
||||||
*)
|
*)
|
||||||
stop "onbekende optie '$MODUS' (kies: controle, simulator of toestel)"
|
stop "onbekende optie '$MODUS' (kies: controle, simulator, ipad, toestel of archief)"
|
||||||
;;
|
;;
|
||||||
esac
|
esac
|
||||||
|
|||||||