about: link to privacy policy
This commit is contained in:
parent
941288b5fc
commit
f6ac8f5e37
9 changed files with 153 additions and 56 deletions
|
@ -8,7 +8,7 @@ You must use the app for legal, authorized and acceptable purposes.
|
|||
|
||||
The app is released “as-is”, without any warranty, responsibility or liability. Use of the app is at your own risk.
|
||||
|
||||
## Privacy policy
|
||||
## Privacy Policy
|
||||
|
||||
The app does not collect any personal data. We never have access to your photos and videos. This also means that we cannot get them back for you if you delete them without backing them up.
|
||||
|
||||
|
|
|
@ -451,6 +451,8 @@
|
|||
"@aboutLinkSources": {},
|
||||
"aboutLinkLicense": "License",
|
||||
"@aboutLinkLicense": {},
|
||||
"aboutLinkPolicy": "Privacy Policy",
|
||||
"@aboutLinkPolicy": {},
|
||||
|
||||
"aboutUpdate": "New Version Available",
|
||||
"@aboutUpdate": {},
|
||||
|
@ -504,6 +506,9 @@
|
|||
"aboutLicensesShowAllButtonLabel": "Show All Licenses",
|
||||
"@aboutLicensesShowAllButtonLabel": {},
|
||||
|
||||
"policyPageTitle": "Privacy Policy",
|
||||
"@policyPageTitle": {},
|
||||
|
||||
"collectionPageTitle": "Collection",
|
||||
"@collectionPageTitle": {},
|
||||
"collectionPickPageTitle": "Pick",
|
||||
|
|
|
@ -203,6 +203,7 @@
|
|||
"aboutPageTitle": "앱 정보",
|
||||
"aboutLinkSources": "소스 코드",
|
||||
"aboutLinkLicense": "라이선스",
|
||||
"aboutLinkPolicy": "개인정보 보호정책",
|
||||
|
||||
"aboutUpdate": "업데이트 사용 가능",
|
||||
"aboutUpdateLinks1": "앱의 최신 버전을",
|
||||
|
@ -232,6 +233,8 @@
|
|||
"aboutLicensesDartPackages": "다트 패키지",
|
||||
"aboutLicensesShowAllButtonLabel": "라이선스 모두 보기",
|
||||
|
||||
"policyPageTitle": "개인정보 보호정책",
|
||||
|
||||
"collectionPageTitle": "미디어",
|
||||
"collectionPickPageTitle": "항목 선택",
|
||||
"collectionSelectionPageTitle": "{count, plural, =0{항목 선택} other{{count}개}}",
|
||||
|
|
|
@ -2,6 +2,7 @@ import 'dart:ui';
|
|||
|
||||
import 'package:aves/theme/icons.dart';
|
||||
import 'package:aves/utils/constants.dart';
|
||||
import 'package:aves/widgets/about/policy_page.dart';
|
||||
import 'package:aves/widgets/common/basic/link_chip.dart';
|
||||
import 'package:aves/widgets/common/extensions/build_context.dart';
|
||||
import 'package:aves/widgets/common/identity/aves_logo.dart';
|
||||
|
@ -66,16 +67,18 @@ class _AppReferenceState extends State<AppReference> {
|
|||
}
|
||||
|
||||
Widget _buildLinks() {
|
||||
final l10n = context.l10n;
|
||||
return Wrap(
|
||||
crossAxisAlignment: WrapCrossAlignment.center,
|
||||
alignment: WrapAlignment.center,
|
||||
spacing: 16,
|
||||
crossAxisAlignment: WrapCrossAlignment.center,
|
||||
children: [
|
||||
LinkChip(
|
||||
leading: const Icon(
|
||||
AIcons.github,
|
||||
size: 24,
|
||||
),
|
||||
text: context.l10n.aboutLinkSources,
|
||||
text: l10n.aboutLinkSources,
|
||||
url: Constants.avesGithub,
|
||||
),
|
||||
LinkChip(
|
||||
|
@ -83,10 +86,28 @@ class _AppReferenceState extends State<AppReference> {
|
|||
AIcons.legal,
|
||||
size: 22,
|
||||
),
|
||||
text: context.l10n.aboutLinkLicense,
|
||||
text: l10n.aboutLinkLicense,
|
||||
url: '${Constants.avesGithub}/blob/main/LICENSE',
|
||||
),
|
||||
LinkChip(
|
||||
leading: const Icon(
|
||||
AIcons.privacy,
|
||||
size: 22,
|
||||
),
|
||||
text: l10n.aboutLinkPolicy,
|
||||
onTap: _goToPolicyPage,
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
void _goToPolicyPage() {
|
||||
Navigator.push(
|
||||
context,
|
||||
MaterialPageRoute(
|
||||
settings: const RouteSettings(name: PolicyPage.routeName),
|
||||
builder: (context) => const PolicyPage(),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
49
lib/widgets/about/policy_page.dart
Normal file
49
lib/widgets/about/policy_page.dart
Normal file
|
@ -0,0 +1,49 @@
|
|||
import 'package:aves/widgets/common/basic/markdown_container.dart';
|
||||
import 'package:aves/widgets/common/extensions/build_context.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
|
||||
class PolicyPage extends StatefulWidget {
|
||||
static const routeName = '/about/policy';
|
||||
|
||||
const PolicyPage({
|
||||
Key? key,
|
||||
}) : super(key: key);
|
||||
|
||||
@override
|
||||
_PolicyPageState createState() => _PolicyPageState();
|
||||
}
|
||||
|
||||
class _PolicyPageState extends State<PolicyPage> {
|
||||
late Future<String> _termsLoader;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_termsLoader = rootBundle.loadString('assets/terms.md');
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
title: Text(context.l10n.policyPageTitle),
|
||||
),
|
||||
body: SafeArea(
|
||||
child: Center(
|
||||
child: FutureBuilder<String>(
|
||||
future: _termsLoader,
|
||||
builder: (context, snapshot) {
|
||||
if (snapshot.hasError || snapshot.connectionState != ConnectionState.done) return const SizedBox();
|
||||
final terms = snapshot.data!;
|
||||
return Padding(
|
||||
padding: const EdgeInsets.symmetric(vertical: 8),
|
||||
child: MarkdownContainer(data: terms),
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
|
@ -5,9 +5,10 @@ import 'package:url_launcher/url_launcher.dart';
|
|||
class LinkChip extends StatelessWidget {
|
||||
final Widget? leading;
|
||||
final String text;
|
||||
final String url;
|
||||
final String? url;
|
||||
final Color? color;
|
||||
final TextStyle? textStyle;
|
||||
final VoidCallback? onTap;
|
||||
|
||||
static const borderRadius = BorderRadius.all(Radius.circular(8));
|
||||
|
||||
|
@ -15,22 +16,25 @@ class LinkChip extends StatelessWidget {
|
|||
Key? key,
|
||||
this.leading,
|
||||
required this.text,
|
||||
required this.url,
|
||||
this.url,
|
||||
this.color,
|
||||
this.textStyle,
|
||||
this.onTap,
|
||||
}) : super(key: key);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final _url = url;
|
||||
return DefaultTextStyle.merge(
|
||||
style: (textStyle ?? const TextStyle()).copyWith(color: color),
|
||||
child: InkWell(
|
||||
borderRadius: borderRadius,
|
||||
onTap: () async {
|
||||
if (await canLaunch(url)) {
|
||||
await launch(url);
|
||||
}
|
||||
},
|
||||
onTap: onTap ??
|
||||
() async {
|
||||
if (_url != null && await canLaunch(_url)) {
|
||||
await launch(_url);
|
||||
}
|
||||
},
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(8.0),
|
||||
child: Row(
|
||||
|
|
53
lib/widgets/common/basic/markdown_container.dart
Normal file
53
lib/widgets/common/basic/markdown_container.dart
Normal file
|
@ -0,0 +1,53 @@
|
|||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_markdown/flutter_markdown.dart';
|
||||
import 'package:url_launcher/url_launcher.dart';
|
||||
|
||||
class MarkdownContainer extends StatelessWidget {
|
||||
final String data;
|
||||
|
||||
const MarkdownContainer({
|
||||
Key? key,
|
||||
required this.data,
|
||||
}) : super(key: key);
|
||||
|
||||
static const double maxWidth = 460;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Container(
|
||||
margin: const EdgeInsets.symmetric(horizontal: 8),
|
||||
decoration: const BoxDecoration(
|
||||
borderRadius: BorderRadius.all(Radius.circular(16)),
|
||||
color: Colors.white10,
|
||||
),
|
||||
constraints: const BoxConstraints(maxWidth: maxWidth),
|
||||
child: ClipRRect(
|
||||
borderRadius: const BorderRadius.all(Radius.circular(16)),
|
||||
child: Theme(
|
||||
data: Theme.of(context).copyWith(
|
||||
scrollbarTheme: const ScrollbarThemeData(
|
||||
isAlwaysShown: true,
|
||||
radius: Radius.circular(16),
|
||||
crossAxisMargin: 6,
|
||||
mainAxisMargin: 16,
|
||||
interactive: true,
|
||||
),
|
||||
),
|
||||
child: Scrollbar(
|
||||
child: Markdown(
|
||||
data: data,
|
||||
selectable: true,
|
||||
onTapLink: (text, href, title) async {
|
||||
if (href != null && await canLaunch(href)) {
|
||||
await launch(href);
|
||||
}
|
||||
},
|
||||
shrinkWrap: true,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
|
@ -1,6 +1,7 @@
|
|||
import 'package:aves/app_flavor.dart';
|
||||
import 'package:aves/model/settings/settings.dart';
|
||||
import 'package:aves/theme/durations.dart';
|
||||
import 'package:aves/widgets/common/basic/markdown_container.dart';
|
||||
import 'package:aves/widgets/common/extensions/build_context.dart';
|
||||
import 'package:aves/widgets/common/identity/aves_logo.dart';
|
||||
import 'package:aves/widgets/common/identity/buttons.dart';
|
||||
|
@ -10,10 +11,8 @@ import 'package:flutter/foundation.dart';
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/scheduler.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:flutter_markdown/flutter_markdown.dart';
|
||||
import 'package:flutter_staggered_animations/flutter_staggered_animations.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:url_launcher/url_launcher.dart';
|
||||
|
||||
class WelcomePage extends StatefulWidget {
|
||||
const WelcomePage({Key? key}) : super(key: key);
|
||||
|
@ -26,8 +25,6 @@ class _WelcomePageState extends State<WelcomePage> {
|
|||
bool _hasAcceptedTerms = false;
|
||||
late Future<String> _termsLoader;
|
||||
|
||||
static const double maxWidth = 460;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
|
@ -44,7 +41,7 @@ class _WelcomePageState extends State<WelcomePage> {
|
|||
child: FutureBuilder<String>(
|
||||
future: _termsLoader,
|
||||
builder: (context, snapshot) {
|
||||
if (snapshot.hasError || snapshot.connectionState != ConnectionState.done) return const SizedBox.shrink();
|
||||
if (snapshot.hasError || snapshot.connectionState != ConnectionState.done) return const SizedBox();
|
||||
final terms = snapshot.data!;
|
||||
final durations = context.watch<DurationsData>();
|
||||
final isPortrait = context.select<MediaQueryData, Orientation>((mq) => mq.orientation) == Orientation.portrait;
|
||||
|
@ -62,7 +59,7 @@ class _WelcomePageState extends State<WelcomePage> {
|
|||
children: [
|
||||
..._buildHeader(context, isPortrait: isPortrait),
|
||||
if (isPortrait) ...[
|
||||
Flexible(child: _buildTerms(terms)),
|
||||
Flexible(child: MarkdownContainer(data: terms)),
|
||||
const SizedBox(height: 16),
|
||||
..._buildControls(context),
|
||||
] else
|
||||
|
@ -72,7 +69,7 @@ class _WelcomePageState extends State<WelcomePage> {
|
|||
Flexible(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.only(bottom: 8),
|
||||
child: _buildTerms(terms),
|
||||
child: MarkdownContainer(data: terms),
|
||||
)),
|
||||
Flexible(
|
||||
child: ListView(
|
||||
|
@ -127,7 +124,7 @@ class _WelcomePageState extends State<WelcomePage> {
|
|||
final canEnableErrorReporting = context.select<AppFlavor, bool>((v) => v.canEnableErrorReporting);
|
||||
const contentPadding = EdgeInsets.symmetric(horizontal: 8);
|
||||
final switches = ConstrainedBox(
|
||||
constraints: const BoxConstraints(maxWidth: maxWidth),
|
||||
constraints: const BoxConstraints(maxWidth: MarkdownContainer.maxWidth),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
|
@ -183,43 +180,6 @@ class _WelcomePageState extends State<WelcomePage> {
|
|||
];
|
||||
}
|
||||
|
||||
Widget _buildTerms(String terms) {
|
||||
return Container(
|
||||
margin: const EdgeInsets.symmetric(horizontal: 8),
|
||||
decoration: const BoxDecoration(
|
||||
borderRadius: BorderRadius.all(Radius.circular(16)),
|
||||
color: Colors.white10,
|
||||
),
|
||||
constraints: const BoxConstraints(maxWidth: maxWidth),
|
||||
child: ClipRRect(
|
||||
borderRadius: const BorderRadius.all(Radius.circular(16)),
|
||||
child: Theme(
|
||||
data: Theme.of(context).copyWith(
|
||||
scrollbarTheme: const ScrollbarThemeData(
|
||||
isAlwaysShown: true,
|
||||
radius: Radius.circular(16),
|
||||
crossAxisMargin: 6,
|
||||
mainAxisMargin: 16,
|
||||
interactive: true,
|
||||
),
|
||||
),
|
||||
child: Scrollbar(
|
||||
child: Markdown(
|
||||
data: terms,
|
||||
selectable: true,
|
||||
onTapLink: (text, href, title) async {
|
||||
if (href != null && await canLaunch(href)) {
|
||||
await launch(href);
|
||||
}
|
||||
},
|
||||
shrinkWrap: true,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
// as of flutter_staggered_animations v0.1.2, `AnimationConfiguration.toStaggeredList` does not handle `Flexible` widgets
|
||||
// so we use this workaround instead
|
||||
static List<Widget> _toStaggeredList({
|
||||
|
|
|
@ -17,7 +17,9 @@
|
|||
"unsupportedTypeDialogTitle",
|
||||
"unsupportedTypeDialogMessage",
|
||||
"editEntryDateDialogExtractFromTitle",
|
||||
"aboutLinkPolicy",
|
||||
"aboutCreditsTranslators",
|
||||
"policyPageTitle",
|
||||
"collectionActionEdit",
|
||||
"collectionEditFailureFeedback",
|
||||
"collectionEditSuccessFeedback",
|
||||
|
|
Loading…
Reference in a new issue