Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
32 changes: 32 additions & 0 deletions lib/Models/ThemeConfig.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import 'package:flutter/material.dart';

class ThemeConfig {
final Color primaryColor;
final double textSizeMultiplier;

ThemeConfig({
required this.primaryColor,
required this.textSizeMultiplier,
});

ThemeData get themeData {
return ThemeData(
colorScheme: ColorScheme.fromSeed(seedColor: primaryColor),
useMaterial3: true,
textTheme: const TextTheme().apply(
fontSizeFactor: textSizeMultiplier,
),
);
}

// Helper to create a copy with some changed fields
ThemeConfig copyWith({
Color? primaryColor,
double? textSizeMultiplier,
}) {
return ThemeConfig(
primaryColor: primaryColor ?? this.primaryColor,
textSizeMultiplier: textSizeMultiplier ?? this.textSizeMultiplier,
);
}
}
41 changes: 41 additions & 0 deletions lib/Services/ConfigProvider.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import 'package:flutter/material.dart';
import '../Models/ThemeConfig.dart';
import 'SettingsManager.dart';

class ConfigProvider with ChangeNotifier {
ThemeConfig _themeConfig = ThemeConfig(
primaryColor: Colors.deepPurple,
textSizeMultiplier: 1.0,
);

ThemeConfig get themeConfig => _themeConfig;

Future<void> loadSettings() async {
final settingsManager = await SettingsManager.getInstance();
String colorHex = await settingsManager.getPrimaryColor();
double textSizeMultiplier = await settingsManager.getTextSizeMultiplier();

_themeConfig = ThemeConfig(
primaryColor: Color(int.parse(colorHex, radix: 16)),
textSizeMultiplier: textSizeMultiplier,
);
notifyListeners();
}

Future<void> updateTheme({Color? primaryColor, double? textSizeMultiplier}) async {
_themeConfig = _themeConfig.copyWith(
primaryColor: primaryColor,
textSizeMultiplier: textSizeMultiplier,
);

final settingsManager = await SettingsManager.getInstance();
if (primaryColor != null) {
await settingsManager.setPrimaryColor(primaryColor.value.toRadixString(16).toUpperCase());
}
if (textSizeMultiplier != null) {
await settingsManager.setTextSizeMultiplier(textSizeMultiplier);
}

notifyListeners();
}
}
19 changes: 19 additions & 0 deletions lib/Services/SettingsManager.dart
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,9 @@ import 'package:shared_preferences/shared_preferences.dart';

class SettingsManager {
static const String _backendUrlKey = 'backend_url';
static const String _primaryColorKey = 'primary_color';
static const String _textSizeMultiplierKey = 'text_size_multiplier';

static SettingsManager? _instance;
static SharedPreferences? _prefs;

Expand All @@ -23,6 +26,22 @@ class SettingsManager {
return _prefs?.getString(_backendUrlKey) ?? 'http://127.0.0.1:5000';
}

Future<void> setPrimaryColor(String colorHex) async {
await _prefs?.setString(_primaryColorKey, colorHex);
}

Future<String> getPrimaryColor() async {
return _prefs?.getString(_primaryColorKey) ?? 'FF673AB7'; // Default DeepPurple
}

Future<void> setTextSizeMultiplier(double multiplier) async {
await _prefs?.setDouble(_textSizeMultiplierKey, multiplier);
}

Future<double> getTextSizeMultiplier() async {
return _prefs?.getDouble(_textSizeMultiplierKey) ?? 1.0;
}

Future<void> clearAllSettings() async {
await _prefs?.clear();
}
Expand Down
30 changes: 21 additions & 9 deletions lib/main.dart
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import 'package:exdata_collector/Services/LocalDatabaseService/LocalDataManager.dart';
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'Services/ConfigProvider.dart';
import 'Models/Boat.dart';
import 'Models/Race.dart';
import 'Services/OnlineSaver.dart';
Expand All @@ -11,22 +13,32 @@ import "Models/Run.dart";
import 'settingsScreen.dart';
import 'raceRunList.dart';

void main() {
runApp(const MyApp());
void main() async {
WidgetsFlutterBinding.ensureInitialized();
final configProvider = ConfigProvider();
await configProvider.loadSettings();

runApp(
ChangeNotifierProvider(
create: (context) => configProvider,
child: const MyApp(),
),
);
}

class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);

@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'EXCategory Data Saver',
theme: ThemeData(
colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple),
useMaterial3: true,
),
home: const MyHomePage(title: 'EXCategory Data Saver'),
return Consumer<ConfigProvider>(
builder: (context, config, child) {
return MaterialApp(
title: 'EXCategory Data Saver',
theme: config.themeConfig.themeData,
home: const MyHomePage(title: 'EXCategory Data Saver'),
);
},
);
}
}
Expand Down
110 changes: 85 additions & 25 deletions lib/settingsScreen.dart
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import 'package:flutter/material.dart';
import 'package:exdata_collector/Services/SettingsManager.dart';
import 'package:exdata_collector/Services/OnlineSaver.dart';
import 'package:provider/provider.dart';
import 'Services/ConfigProvider.dart';

class SettingsScreen extends StatefulWidget {
const SettingsScreen({Key? key}) : super(key: key);
Expand All @@ -11,6 +13,7 @@ class SettingsScreen extends StatefulWidget {

class _SettingsScreenState extends State<SettingsScreen> {
final TextEditingController _urlController = TextEditingController();
final TextEditingController _colorController = TextEditingController();
late SettingsManager _settingsManager;

@override
Expand All @@ -21,19 +24,22 @@ class _SettingsScreenState extends State<SettingsScreen> {

void _initializeSettings() async {
_settingsManager = await SettingsManager.getInstance();
_loadCurrentUrl();
_loadSettings();
}

void _loadCurrentUrl() async {
void _loadSettings() async {
String url = await _settingsManager.getBackendUrl();
String colorHex = await _settingsManager.getPrimaryColor();
setState(() {
_urlController.text = url;
_colorController.text = colorHex;
});
}

@override
void dispose() {
_urlController.dispose();
_colorController.dispose();
super.dispose();
}

Expand All @@ -60,30 +66,84 @@ class _SettingsScreenState extends State<SettingsScreen> {
appBar: AppBar(
title: const Text('Settings'),
),
body: Padding(
padding: const EdgeInsets.all(16.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const Text(
'Backend URL:',
style: TextStyle(fontSize: 16, fontWeight: FontWeight.bold),
body: Consumer<ConfigProvider>(
builder: (context, config, child) {
return SingleChildScrollView(
padding: const EdgeInsets.all(16.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const Text(
'Backend URL:',
style: TextStyle(fontSize: 16, fontWeight: FontWeight.bold),
),
const SizedBox(height: 10),
TextFormField(
controller: _urlController,
decoration: const InputDecoration(
hintText: 'http://127.0.0.1:5000',
border: OutlineInputBorder(),
),
),
const SizedBox(height: 20),
ElevatedButton(
onPressed: _saveUrl,
child: const Text('Save URL'),
),
const Divider(height: 40),
const Text(
'Theme Color (Hex ARGB):',
style: TextStyle(fontSize: 16, fontWeight: FontWeight.bold),
),
const SizedBox(height: 10),
TextFormField(
controller: _colorController,
decoration: const InputDecoration(
hintText: 'FF673AB7',
border: OutlineInputBorder(),
),
onFieldSubmitted: (value) {
try {
Color color = Color(int.parse(value, radix: 16));
config.updateTheme(primaryColor: color);
} catch (e) {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(content: Text('Invalid color format')),
);
}
},
),
const SizedBox(height: 20),
ElevatedButton(
onPressed: () {
try {
Color color = Color(int.parse(_colorController.text, radix: 16));
config.updateTheme(primaryColor: color);
} catch (e) {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(content: Text('Invalid color format')),
);
}
},
child: const Text('Update Color'),
),
const Divider(height: 40),
Text(
'Text Size Multiplier: ${config.themeConfig.textSizeMultiplier.toStringAsFixed(2)}',
style: TextStyle(fontSize: 16, fontWeight: FontWeight.bold),
),
Slider(
value: config.themeConfig.textSizeMultiplier,
min: 0.5,
max: 2.0,
onChanged: (value) {
config.updateTheme(textSizeMultiplier: value);
},
),
],
),
const SizedBox(height: 10),
TextFormField(
controller: _urlController,
decoration: const InputDecoration(
hintText: 'http://127.0.0.1:5000',
border: OutlineInputBorder(),
),
),
const SizedBox(height: 20),
ElevatedButton(
onPressed: _saveUrl,
child: const Text('Save'),
),
],
),
);
},
),
);
}
Expand Down
16 changes: 16 additions & 0 deletions pubspec.lock
Original file line number Diff line number Diff line change
Expand Up @@ -296,6 +296,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "2.0.0"
nested:
dependency: transitive
description:
name: nested
sha256: "03bac4c528c64c95c722ec99280375a6f2fc708eec17c7b3f07253b626cd2a20"
url: "https://pub.dev"
source: hosted
version: "1.0.0"
path:
dependency: transitive
description:
Expand Down Expand Up @@ -368,6 +376,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "2.1.8"
provider:
dependency: "direct main"
description:
name: provider
sha256: "4e82183fa20e5ca25703ead7e05de9e4cceed1fbd1eadc1ac3cb6f565a09f272"
url: "https://pub.dev"
source: hosted
version: "6.1.5+1"
shared_preferences:
dependency: "direct main"
description:
Expand Down
1 change: 1 addition & 0 deletions pubspec.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ dependencies:
http: ^1.2.1
path_provider: ^2.1.2
image_picker: ^1.0.4
provider: ^6.1.1

# The following adds the Cupertino Icons font to your application.
# Use with the CupertinoIcons class for iOS style icons.
Expand Down
11 changes: 10 additions & 1 deletion test/widget_test.dart
Original file line number Diff line number Diff line change
@@ -1,10 +1,19 @@
import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:exdata_collector/main.dart';
import 'package:exdata_collector/Services/ConfigProvider.dart';
import 'package:provider/provider.dart';

void main() {
testWidgets('App starts smoke test', (WidgetTester tester) async {
final configProvider = ConfigProvider();
// Build our app and trigger a frame.
await tester.pumpWidget(const MyApp());
await tester.pumpWidget(
ChangeNotifierProvider(
create: (context) => configProvider,
child: const MyApp(),
),
);

// Verify that our app title is present.
expect(find.text('EXCategory Data Saver'), findsAtLeastNWidgets(1));
Expand Down
Loading