Skip to content

Commit

Permalink
Allow setting the theme and language
Browse files Browse the repository at this point in the history
  • Loading branch information
Xennis committed Nov 11, 2023
1 parent ca3f515 commit 58eb2dc
Show file tree
Hide file tree
Showing 22 changed files with 368 additions and 110 deletions.
8 changes: 7 additions & 1 deletion green_walking/lib/l10n/app_de.arb
Original file line number Diff line number Diff line change
Expand Up @@ -50,5 +50,11 @@
"openLocationInDefaultAppSemanticLabel": "Öffnen mit Standardapp",
"openLocationDetailsSemanticLabel": "Details anzeigen",
"followLocationToast": "Position folgen",
"followLocationOffToast": "Position folgen aus"
"followLocationOffToast": "Position folgen aus",
"optionDarkTheme": "Dark Theme",
"optionTheme": "Design",
"optionDarkTheme": "Dunkel",
"optionLightTheme": "Hell",
"optionSystem": "System",
"language": "Sprache"
}
19 changes: 18 additions & 1 deletion green_walking/lib/l10n/app_en.arb
Original file line number Diff line number Diff line change
Expand Up @@ -338,5 +338,22 @@
"followLocationOffToast": "Follow location off",
"@followLocationOffToast": {
"description": "Toast message if the map stops following the users location."
}
},
"optionTheme": "Theme",
"@optionDarkTheme": {
"description": "Option to select the theme of the application."
},
"optionDarkTheme": "Dark",
"@optionDarkTheme": {
"description": "Label for the dark theme."
},
"optionLightTheme": "Light",
"@optionLightTheme": {
"description": "Label for the light theme."
},
"optionSystem": "System",
"@optionSystem": {
"description": "Label for using the language/theme/... of the operating system."
},
"language": "Language"
}
56 changes: 32 additions & 24 deletions green_walking/lib/main.dart
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,10 @@ import 'package:firebase_crashlytics/firebase_crashlytics.dart';
import 'package:flutter/material.dart';
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
import 'package:flutter_localizations/flutter_localizations.dart';
import 'package:provider/provider.dart';

import 'firebase_options.dart';
import 'provider/prefs_provider.dart';
import 'routes.dart';

Future<void> main() async {
Expand All @@ -24,29 +26,35 @@ class GreenWalkingApp extends StatelessWidget {

@override
Widget build(BuildContext context) {
final ThemeData theme = ThemeData();
return MaterialApp(
onGenerateTitle: (BuildContext context) => AppLocalizations.of(context)!.appTitle,
theme: theme.copyWith(
primaryColor: Colors.green,
colorScheme: theme.colorScheme.copyWith(secondary: Colors.blue),
// This makes the visual density adapt to the platform that you run
// the app on. For desktop platforms, the controls will be smaller and
// closer together (more dense) than on mobile platforms.
visualDensity: VisualDensity.adaptivePlatformDensity,
),
initialRoute: Routes.map,
routes: () {
final Map<String, WidgetBuilder> routes = getRoutes(context);
return routes;
}(),
localizationsDelegates: const <LocalizationsDelegate<dynamic>>[
AppLocalizations.delegate,
GlobalMaterialLocalizations.delegate,
GlobalWidgetsLocalizations.delegate,
GlobalCupertinoLocalizations.delegate,
],
supportedLocales: AppLocalizations.supportedLocales,
);
return ChangeNotifierProvider<AppPrefsProvider>(
create: (_) => AppPrefsProvider(true),
child: Builder(builder: (context) {
final AppPrefsProvider prefsProvider = Provider.of<AppPrefsProvider>(context);
return MaterialApp(
onGenerateTitle: (BuildContext context) => AppLocalizations.of(context)!.appTitle,
theme: ThemeData(
brightness: Brightness.light,
primaryColor: Colors.blue,
),
darkTheme: ThemeData(
brightness: Brightness.dark,
primaryColor: Colors.blue,
),
themeMode: prefsProvider.themeMode,
initialRoute: Routes.map,
routes: () {
final Map<String, WidgetBuilder> routes = getRoutes(context);
return routes;
}(),
locale: prefsProvider.locale,
localizationsDelegates: const <LocalizationsDelegate<dynamic>>[
AppLocalizations.delegate,
GlobalMaterialLocalizations.delegate,
GlobalWidgetsLocalizations.delegate,
GlobalCupertinoLocalizations.delegate,
],
supportedLocales: AppLocalizations.supportedLocales,
);
}));
}
}
4 changes: 3 additions & 1 deletion green_walking/lib/pages/feedback.dart
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ class FeedbackPage extends StatelessWidget {
@override
Widget build(BuildContext context) {
final AppLocalizations locale = AppLocalizations.of(context)!;
final ThemeData theme = Theme.of(context);

return Scaffold(
appBar: AppBar(
title: Text(locale.feedbackPage),
Expand All @@ -21,7 +23,7 @@ class FeedbackPage extends StatelessWidget {
children: <Widget>[
RichText(
text: TextSpan(
style: const TextStyle(color: Colors.black),
style: TextStyle(color: theme.unselectedWidgetColor),
children: <InlineSpan>[
TextSpan(
text: '${locale.feedbackPageText}\n',
Expand Down
6 changes: 4 additions & 2 deletions green_walking/lib/pages/imprint.dart
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ class ImprintPage extends StatelessWidget {
@override
Widget build(BuildContext context) {
final AppLocalizations locale = AppLocalizations.of(context)!;
final ThemeData theme = Theme.of(context);

return Scaffold(
appBar: AppBar(
title: Text(locale.imprint),
Expand All @@ -22,7 +24,7 @@ class ImprintPage extends StatelessWidget {
children: <Widget>[
RichText(
text: TextSpan(
style: const TextStyle(color: Colors.black),
style: TextStyle(color: theme.unselectedWidgetColor),
children: <InlineSpan>[
TextSpan(
text: '${locale.imprintTmgText('5')}:\n\n',
Expand All @@ -40,7 +42,7 @@ class ImprintPage extends StatelessWidget {
TextSpan(text: '${locale.imprintGdprApplyText} '),
TextSpan(
text: locale.gdprPrivacyPolicy,
style: const TextStyle(color: Colors.blue),
style: TextStyle(color: theme.primaryColor),
recognizer: TapGestureRecognizer()..onTap = () => launchUrl(privacyPolicyUrl),
),
const TextSpan(text: '.')
Expand Down
4 changes: 2 additions & 2 deletions green_walking/lib/pages/map.dart
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import 'package:flutter/material.dart';
import 'package:mapbox_maps_flutter/mapbox_maps_flutter.dart' show CameraOptions, CameraState, Position;

import 'search.dart';
import '../services/shared_prefs.dart';
import '../services/app_prefs.dart';
import '../widgets/gdpr_dialog.dart';
import '../widgets/map_view.dart';
import '../widgets/navigation_drawer.dart';
Expand Down Expand Up @@ -80,7 +80,7 @@ class _MapConfig {

static Future<_MapConfig> create(AssetBundle assetBundle) async {
final String accessToken = await assetBundle.loadString('assets/mapbox-access-token.txt');
final CameraState? lastState = await SharedPrefs.getCameraState(SharedPrefs.keyLastPosition);
final CameraState? lastState = await AppPrefs.getCameraState(AppPrefs.keyLastPosition);
if (lastState == null) {
return _MapConfig(accessToken, lastPosition: null);
}
Expand Down
8 changes: 5 additions & 3 deletions green_walking/lib/pages/search.dart
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ class _SearchPageState extends State<SearchPage> {
@override
Widget build(BuildContext context) {
final AppLocalizations locale = AppLocalizations.of(context)!;
final ThemeData theme = Theme.of(context);

return Scaffold(
// If the search in the search bar is clicked the keyboard appears. The keyboard
Expand All @@ -53,13 +54,12 @@ class _SearchPageState extends State<SearchPage> {
children: <Widget>[
MapAppBar(
leading: IconButton(
splashColor: Colors.grey,
icon: Icon(Icons.arrow_back, semanticLabel: MaterialLocalizations.of(context).backButtonTooltip),
onPressed: () => Navigator.pop(context)),
title: TextField(
controller: _queryFieldController,
autofocus: true,
cursorColor: Colors.black,
cursorColor: theme.hintColor,
keyboardType: TextInputType.text,
textInputAction: TextInputAction.go,
decoration: InputDecoration(
Expand Down Expand Up @@ -94,6 +94,8 @@ class _SearchPageState extends State<SearchPage> {
return Container();
}
final AppLocalizations locale = AppLocalizations.of(context)!;
final ThemeData theme = Theme.of(context);

return FutureBuilder<GeocodingResult>(
future: _result,
builder: (BuildContext context, AsyncSnapshot<GeocodingResult> snapshot) {
Expand Down Expand Up @@ -129,7 +131,7 @@ class _SearchPageState extends State<SearchPage> {
children: [
Flexible(
child: Text(locale.geocodingResultLegalNotice(data.attribution),
style: const TextStyle(color: Colors.grey, fontSize: 12.0)))
style: TextStyle(color: theme.hintColor, fontSize: 12.0)))
],
),
],
Expand Down
75 changes: 19 additions & 56 deletions green_walking/lib/pages/settings.dart
Original file line number Diff line number Diff line change
@@ -1,69 +1,32 @@
import 'package:firebase_analytics/firebase_analytics.dart';
import 'package:flutter/material.dart';
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
import 'package:provider/provider.dart';

import '../services/shared_prefs.dart';
import '../provider/prefs_provider.dart';
import '../widgets/settings/analytics_list_tile.dart';
import '../widgets/settings/language_list_tile.dart';
import '../widgets/settings/theme_list_tile.dart';

class SettingsPage extends StatefulWidget {
class SettingsPage extends StatelessWidget {
const SettingsPage({super.key});

@override
State<SettingsPage> createState() => _SettingsPageState();
}

class _SettingsPageState extends State<SettingsPage> {
bool _analyticsEnabled = false;

@override
Widget build(BuildContext context) {
final AppLocalizations locale = AppLocalizations.of(context)!;
final AppPrefsProvider prefsProvider = Provider.of<AppPrefsProvider>(context);

return Scaffold(
appBar: AppBar(
title: Text(locale.settingsPage),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.start,
children: <Widget>[
Padding(
padding: const EdgeInsets.fromLTRB(15, 25, 15, 25),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: <Widget>[
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
const Text('Google Analytics', style: TextStyle(fontSize: 16.0)),
Text(
locale.settingsTrackingDescription,
style: const TextStyle(color: Colors.grey),
),
],
),
FutureBuilder<bool?>(
future: SharedPrefs.getBool(SharedPrefs.analyticsEnabled),
initialData: _analyticsEnabled,
builder: (BuildContext context, AsyncSnapshot<bool?> snapshot) {
return Switch(
value: snapshot.data ?? false,
onChanged: (bool? value) {
if (value == null) {
return;
}
SharedPrefs.setBool(SharedPrefs.analyticsEnabled, value);
FirebaseAnalytics.instance.setAnalyticsCollectionEnabled(value);
setState(() {
_analyticsEnabled = value;
});
},
);
}),
],
),
)
],
appBar: AppBar(
title: Text(locale.settingsPage),
),
),
);
body: SingleChildScrollView(
child: Padding(
padding: const EdgeInsets.fromLTRB(10, 20, 10, 20),
child: Column(children: [
ThemeListTile(prefsProvider.themeMode),
LanguageListTile(prefsProvider.locale),
const Divider(),
const AnalyticsListTile()
]))));
}
}
32 changes: 32 additions & 0 deletions green_walking/lib/provider/prefs_provider.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import 'package:flutter/material.dart';
import 'package:green_walking/services/app_prefs.dart';

class AppPrefsProvider extends ChangeNotifier {
AppPrefsProvider(bool load) {
if (load) {
// Can be disabled for testing reasons.
_loadFromPrefs();
}
}

ThemeMode? themeMode;
Locale? locale;

void setThemeMode(ThemeMode? value) async {
themeMode = value;
await AppPrefs.setThemeMode(value);
notifyListeners();
}

void setLanguage(Locale? value) async {
locale = value;
await AppPrefs.setLocale(value);
notifyListeners();
}

_loadFromPrefs() async {
themeMode = await AppPrefs.getThemeMode();
locale = await AppPrefs.getLocale();
notifyListeners();
}
}
Original file line number Diff line number Diff line change
@@ -1,14 +1,18 @@
import 'dart:convert';
import 'dart:developer' show log;

import 'package:flutter/material.dart' show Locale, ThemeMode;
import 'package:mapbox_maps_flutter/mapbox_maps_flutter.dart' show CameraState;
import 'package:shared_preferences/shared_preferences.dart';

// ignore: avoid_classes_with_only_static_members
class SharedPrefs {
class AppPrefs {
static const String keyLastPosition = 'last-position';
static const String analyticsEnabled = 'analytics-enabled';

static const String _keyThemeMode = 'themeMode';
static const String _keyLanguage = 'language';

static Future<CameraState?> getCameraState(String key) async {
final SharedPreferences prefs = await SharedPreferences.getInstance();
final String? raw = prefs.getString(key);
Expand Down Expand Up @@ -41,4 +45,38 @@ class SharedPrefs {
static void setBool(String key, bool val) {
SharedPreferences.getInstance().then((SharedPreferences prefs) => prefs.setBool(key, val));
}

static Future<ThemeMode?> getThemeMode() async {
final SharedPreferences prefs = await SharedPreferences.getInstance();
final int? raw = prefs.getInt(_keyThemeMode);
if (raw == null) {
return null;
}
return ThemeMode.values[raw];
}

static Future<bool> setThemeMode(ThemeMode? mode) async {
final SharedPreferences prefs = await SharedPreferences.getInstance();
if (mode == null) {
return prefs.remove(_keyThemeMode);
}
return prefs.setInt(_keyThemeMode, mode.index);
}

static Future<Locale?> getLocale() async {
final SharedPreferences prefs = await SharedPreferences.getInstance();
final String? raw = prefs.getString(_keyLanguage);
if (raw == null) {
return null;
}
return Locale(raw);
}

static Future<bool> setLocale(Locale? locale) async {
final SharedPreferences prefs = await SharedPreferences.getInstance();
if (locale == null) {
return prefs.remove(_keyLanguage);
}
return prefs.setString(_keyLanguage, locale.languageCode);
}
}
Loading

0 comments on commit 58eb2dc

Please sign in to comment.