Skip to content

Commit 768fafe

Browse files
committed
[green] prepare build 8 with an offline privacy policy
Signed-off-by: Viwat Vchirawongkwin <viwat.v@chula.ac.th>
1 parent 65032de commit 768fafe

11 files changed

Lines changed: 449 additions & 2 deletions

‎app/lib/app/pages/about_page.dart‎

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import 'package:pyble/localization/localization.dart';
1010
import 'package:pyble/theme/theme.dart';
1111

1212
import '../app_build_info.dart';
13+
import 'privacy_policy_page.dart';
1314

1415
const double _kAboutMaxWidth = 1080;
1516
const double _kAboutTwoColumnMinWidth = 840;
@@ -360,6 +361,20 @@ class _PrivacyCard extends StatelessWidget {
360361
title: l10n.aboutPrivacyTitle,
361362
titleKey: const Key('aboutPrivacyHeading'),
362363
body: l10n.aboutPrivacyBody,
364+
child: Align(
365+
alignment: AlignmentDirectional.centerStart,
366+
child: OutlinedButton.icon(
367+
key: const Key('aboutPrivacyPolicyAction'),
368+
onPressed: () => Navigator.of(context).push<void>(
369+
MaterialPageRoute<void>(
370+
settings: const RouteSettings(name: PrivacyPolicyPage.routeName),
371+
builder: (BuildContext context) => const PrivacyPolicyPage(),
372+
),
373+
),
374+
icon: const Icon(Icons.policy_outlined),
375+
label: Text(l10n.aboutPrivacyPolicyAction),
376+
),
377+
),
363378
);
364379
}
365380
}
Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
// SPDX-License-Identifier: MIT
2+
// Part of PyBLE (https://pyble.dev) — see /LICENSE.
3+
4+
/// X-11 — the complete, bundled policy, available without a board or network.
5+
library;
6+
7+
import 'package:flutter/material.dart';
8+
9+
import 'package:pyble/localization/localization.dart';
10+
import 'package:pyble/theme/theme.dart';
11+
12+
/// Offline counterpart of https://pyble.dev/privacy (FR-ABOUT-9).
13+
class PrivacyPolicyPage extends StatelessWidget {
14+
const PrivacyPolicyPage({super.key});
15+
16+
static const String routeName = '/about/privacy';
17+
// Technical contact identifiers remain verbatim across locales (FR-I18N-4).
18+
static const String _policyUrl = 'https://pyble.dev/privacy';
19+
static const String _contactEmail = 'viwat.v@chula.ac.th';
20+
21+
@override
22+
Widget build(BuildContext context) {
23+
final AppLocalizations l10n = AppLocalizations.of(context);
24+
final TextTheme textTheme = Theme.of(context).textTheme;
25+
final List<({String title, String body})>
26+
sections = <({String title, String body})>[
27+
(title: l10n.privacyMaintainerTitle, body: l10n.privacyMaintainerBody),
28+
(title: l10n.privacyAppTitle, body: l10n.privacyAppBody),
29+
(title: l10n.privacyGithubTitle, body: l10n.privacyGithubBody),
30+
(title: l10n.privacyBoardTitle, body: l10n.privacyBoardBody),
31+
(title: l10n.privacyTransportTitle, body: l10n.privacyTransportBody),
32+
(title: l10n.privacyPermissionsTitle, body: l10n.privacyPermissionsBody),
33+
(title: l10n.privacyRetentionTitle, body: l10n.privacyRetentionBody),
34+
(title: l10n.privacyComponentsTitle, body: l10n.privacyComponentsBody),
35+
(title: l10n.privacyWebsiteTitle, body: l10n.privacyWebsiteBody),
36+
(title: l10n.privacyChangesTitle, body: l10n.privacyChangesBody),
37+
(title: l10n.privacyContactTitle, body: l10n.privacyContactBody),
38+
];
39+
40+
return Scaffold(
41+
key: const Key('privacyPolicyPage'),
42+
appBar: AppBar(title: Text(l10n.privacyPolicyTitle)),
43+
body: SafeArea(
44+
child: SingleChildScrollView(
45+
padding: const EdgeInsets.all(SignalSpacing.lg),
46+
child: Center(
47+
child: ConstrainedBox(
48+
constraints: const BoxConstraints(maxWidth: 800),
49+
child: SelectionArea(
50+
child: Column(
51+
crossAxisAlignment: CrossAxisAlignment.stretch,
52+
children: <Widget>[
53+
Text(l10n.privacyPolicyIntroduction),
54+
const SizedBox(height: SignalSpacing.md),
55+
Text(l10n.privacyPolicyEffectiveDate),
56+
const SizedBox(height: SignalSpacing.sm),
57+
const Text(_policyUrl),
58+
for (final ({String title, String body}) section
59+
in sections) ...<Widget>[
60+
const SizedBox(height: SignalSpacing.xl),
61+
Semantics(
62+
header: true,
63+
child: Text(section.title, style: textTheme.titleLarge),
64+
),
65+
const SizedBox(height: SignalSpacing.sm),
66+
Text(section.body),
67+
],
68+
const SizedBox(height: SignalSpacing.sm),
69+
const Text(_contactEmail),
70+
const SizedBox(height: SignalSpacing.lg),
71+
],
72+
),
73+
),
74+
),
75+
),
76+
),
77+
),
78+
);
79+
}
80+
}

‎app/lib/localization/arb/app_en.arb‎

Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -124,6 +124,110 @@
124124
"@aboutPrivacyBody": {
125125
"description": "Truthful offline-first privacy summary, including the sole optional network workflow."
126126
},
127+
"aboutPrivacyPolicyAction": "Privacy policy",
128+
"@aboutPrivacyPolicyAction": {
129+
"description": "Action opening the complete offline privacy policy."
130+
},
131+
"privacyPolicyTitle": "PyBLE Privacy Policy",
132+
"@privacyPolicyTitle": {
133+
"description": "Title of the bundled offline privacy policy, matching the public policy."
134+
},
135+
"privacyPolicyIntroduction": "PyBLE is designed to work locally. This policy explains what the app handles on your tablet, what you choose to send to a board, and what happens when you visit this website.",
136+
"@privacyPolicyIntroduction": {
137+
"description": "Introduction matching the public privacy policy."
138+
},
139+
"privacyPolicyEffectiveDate": "Effective 28 August 2026",
140+
"@privacyPolicyEffectiveDate": {
141+
"description": "Effective date of the bundled policy; update together with pyble.dev/privacy."
142+
},
143+
"privacyMaintainerTitle": "Who maintains PyBLE",
144+
"@privacyMaintainerTitle": {
145+
"description": "Offline privacy policy section heading: Who maintains PyBLE."
146+
},
147+
"privacyMaintainerBody": "PyBLE is an independent open-source project maintained by Viwat Vchirawongkwin under the SciLabPro project name. It is not an official Chulalongkorn University project or app.",
148+
"@privacyMaintainerBody": {
149+
"description": "Bundled disclosure matching the public privacy policy section: Who maintains PyBLE."
150+
},
151+
"privacyAppTitle": "PyBLE app",
152+
"@privacyAppTitle": {
153+
"description": "Offline privacy policy section heading: PyBLE app."
154+
},
155+
"privacyAppBody": "The PyBLE app has no account, advertising, analytics, telemetry, or crash reporting. It also has no payment or cloud service. The user-started public GitHub import is its only optional Internet workflow. Editing, BLE, Files, Blocks, and Run continue to work offline and remain independent of GitHub availability. The app does not send your project content to PyBLE, SciLabPro, or the maintainer.",
156+
"@privacyAppBody": {
157+
"description": "Bundled disclosure matching the public privacy policy section: PyBLE app."
158+
},
159+
"privacyGithubTitle": "Public GitHub import",
160+
"@privacyGithubTitle": {
161+
"description": "Offline privacy policy section heading: Public GitHub import."
162+
},
163+
"privacyGithubBody": "When you explicitly open Import examples from GitHub, the app makes unauthenticated HTTPS requests only to api.github.com. Those requests include the public repository owner and name, the requested ref, branch discovery operations, directory paths, paths identifying the selected public source files, and Git object identifiers. GitHub returns the selected file contents in its HTTPS responses. Requests also include a bounded PyBLE version in the User-Agent. The app sends no GitHub account or token, no board identity or board files, and no private project source.\n\nGitHub independently receives ordinary request metadata such as your IP address and user-agent under GitHub's own privacy policy. Import only reads the public files you select. It never automatically opens or runs imported code; review the source and choose any later board action yourself.",
164+
"@privacyGithubBody": {
165+
"description": "Bundled disclosure matching the public privacy policy section: Public GitHub import."
166+
},
167+
"privacyBoardTitle": "Data sent to your board",
168+
"@privacyBoardTitle": {
169+
"description": "Offline privacy policy section heading: Data sent to your board."
170+
},
171+
"privacyBoardBody": "When you choose Save, Run, a Files operation, or send console input, PyBLE sends the relevant source code, path or filename, file content, Blocks companion data, or console input directly over BLE to the board you selected. Board output, files, and device information can return to the app as part of the same requested workflow. This is direct communication with your selected hardware, not an upload to a PyBLE or SciLabPro server. PyBLE does not sell this content or share it with advertisers or data brokers.",
172+
"@privacyBoardBody": {
173+
"description": "Bundled disclosure matching the public privacy policy section: Data sent to your board."
174+
},
175+
"privacyTransportTitle": "BLE transport security",
176+
"@privacyTransportTitle": {
177+
"description": "Offline privacy policy section heading: BLE transport security."
178+
},
179+
"privacyTransportBody": "PBLE/1 does not require Bluetooth pairing or BLE link encryption, so a board connection may operate without an authenticated, encrypted link. Do not send passwords, API keys, tokens, private keys, or other secrets unless you have separately verified your physical environment and transport security.",
180+
"@privacyTransportBody": {
181+
"description": "Bundled disclosure matching the public privacy policy section: BLE transport security."
182+
},
183+
"privacyPermissionsTitle": "Nearby boards and platform permissions",
184+
"@privacyPermissionsTitle": {
185+
"description": "Offline privacy policy section heading: Nearby boards and platform permissions."
186+
},
187+
"privacyPermissionsBody": "To discover and connect, the app processes nearby PyBLE board names or labels, platform device identifiers or suffixes, capabilities, and Bluetooth signal strength locally on your tablet. The app uses this information for the connection interface and does not send it to the developer.\n\nOn Android 11 and earlier, the legacy Android location permission is requested only because those Android versions require it for BLE scanning. PyBLE does not derive, store, or transmit your physical location. Android 12 and later use Nearby devices permissions, and iPadOS uses Bluetooth permission, for the same find-and-connect purpose.\n\nIf a board has a custom label, that board may include it in a nearby Bluetooth advertisement visible to other devices in range. Do not put a person's name, email address, or other sensitive information in a board label.",
188+
"@privacyPermissionsBody": {
189+
"description": "Bundled disclosure matching the public privacy policy section: Nearby boards and platform permissions."
190+
},
191+
"privacyRetentionTitle": "Retention and deletion",
192+
"@privacyRetentionTitle": {
193+
"description": "Offline privacy policy section heading: Retention and deletion."
194+
},
195+
"privacyRetentionBody": "App working data stays on your tablet for use by the app. On Android, clear PyBLE's app storage or uninstall it; on iPadOS, delete the app. This removes data held only by the installed app but does not delete files on a board or exported copies. Device and operating-system backup behavior depends on your platform settings.\n\nFiles saved to a board remain there until you overwrite or delete them or erase the board. You can delete board files through Files → Delete in PyBLE or manage and erase the board directly. Because the developer receives no app project content, there is no PyBLE server-side copy of your app project content to retain or delete.",
196+
"@privacyRetentionBody": {
197+
"description": "Bundled disclosure matching the public privacy policy section: Retention and deletion."
198+
},
199+
"privacyComponentsTitle": "Third-party components and platform services",
200+
"@privacyComponentsTitle": {
201+
"description": "Offline privacy policy section heading: Third-party components and platform services."
202+
},
203+
"privacyComponentsBody": "The app contains no advertising, analytics, crash-reporting, or account SDK that receives your app project content. Your operating system and app-distribution service may independently process installation, system, or store information under their own privacy policies.",
204+
"@privacyComponentsBody": {
205+
"description": "Bundled disclosure matching the public privacy policy section: Third-party components and platform services."
206+
},
207+
"privacyWebsiteTitle": "This website",
208+
"@privacyWebsiteTitle": {
209+
"description": "Offline privacy policy section heading: This website."
210+
},
211+
"privacyWebsiteBody": "pyble.dev has no account, advertising, analytics, tracking pixel, contact form, or non-essential cookie. The site does not build a visitor profile.\n\nCloudflare and the VPS hosting infrastructure may process ordinary request data—such as your IP address, user-agent, requested URL, timestamp, and security signals—to deliver the pages, prevent abuse, and keep the service reliable.\n\nThe site does not load marketing scripts, remote web fonts, or embedded social media. Following a link to another site makes that site's policy apply.\n\nWebsite infrastructure may keep limited operational logs according to provider settings and legal obligations. No separate fixed retention period is promised. We use HTTPS and keep the public site static to reduce the data and attack surface involved.",
212+
"@privacyWebsiteBody": {
213+
"description": "Bundled disclosure matching the public privacy policy section: This website."
214+
},
215+
"privacyChangesTitle": "Changes to this policy",
216+
"@privacyChangesTitle": {
217+
"description": "Offline privacy policy section heading: Changes to this policy."
218+
},
219+
"privacyChangesBody": "If a future app or site feature handles information differently— such as analytics, a form, an account, another network integration, or embedded media—we will update this policy and its effective date before that feature is deployed.",
220+
"@privacyChangesBody": {
221+
"description": "Bundled disclosure matching the public privacy policy section: Changes to this policy."
222+
},
223+
"privacyContactTitle": "Privacy and deletion questions",
224+
"@privacyContactTitle": {
225+
"description": "Offline privacy contact heading."
226+
},
227+
"privacyContactBody": "Contact the project maintainer about this policy, a privacy concern, or deletion of website data under the maintainer’s control.",
228+
"@privacyContactBody": {
229+
"description": "Privacy contact scope matching the public policy; verified email is rendered verbatim."
230+
},
127231
"aboutProjectTitle": "Project",
128232
"@aboutProjectTitle": {
129233
"description": "About section heading for project provenance and canonical website."

0 commit comments

Comments
 (0)