Changed default behavior of bottom bar

Commit also includes:
- Initial implementation of shocker limits
- minor code cleanup
- version bump to 2.2

**Release 2.5 due very soon!**
This commit is contained in:
Mercurio 2023-11-13 09:31:28 +01:00
parent 9e1e60492e
commit e3fc6acf32
7 changed files with 178 additions and 114 deletions

View file

@ -1,9 +1,12 @@
import 'dart:convert'; import 'dart:convert';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:http/http.dart' as http; import 'package:http/http.dart' as http;
import 'package:shared_preferences/shared_preferences.dart'; import 'package:shared_preferences/shared_preferences.dart';
import 'package:OpenshockCompanion/settings_page.dart' show SettingsPage; import 'bottom_bar.dart';
import 'app_state.dart'; // Import the AppState class
import 'settings_page.dart';
import 'package:provider/provider.dart';
class LogsPage extends StatefulWidget { class LogsPage extends StatefulWidget {
const LogsPage({Key? key}) : super(key: key); const LogsPage({Key? key}) : super(key: key);
@ -30,7 +33,8 @@ class _LogsPageState extends State<LogsPage> {
return; return;
} }
final url = 'https://api.shocklink.net/1/shockers/$shockerId/logs?offset=0&limit=30'; final url =
'https://api.shocklink.net/1/shockers/$shockerId/logs?offset=0&limit=30';
final response = await http.get(Uri.parse(url), headers: { final response = await http.get(Uri.parse(url), headers: {
'accept': 'application/json', 'accept': 'application/json',
@ -56,6 +60,8 @@ class _LogsPageState extends State<LogsPage> {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final appState = Provider.of<AppState>(context, listen: false);
return Scaffold( return Scaffold(
appBar: AppBar( appBar: AppBar(
title: const Text('Logs'), title: const Text('Logs'),
@ -79,13 +85,15 @@ class _LogsPageState extends State<LogsPage> {
DataColumn(label: Text('Duration (s)')), DataColumn(label: Text('Duration (s)')),
], ],
rows: logs.map((log) { rows: logs.map((log) {
final controlledBy = log['controlledBy'] as Map<String, dynamic>?; final controlledBy =
log['controlledBy'] as Map<String, dynamic>?;
// Add null check for controlledBy // Add null check for controlledBy
if (controlledBy != null) { if (controlledBy != null) {
final name = controlledBy['name'] as String?; final name = controlledBy['name'] as String?;
final intensity = log['intensity'] as int?; final intensity = log['intensity'] as int?;
final duration = (log['duration'] as int?)! / 1000; // Convert to seconds final duration = (log['duration'] as int?)! /
1000; // Convert to seconds
// Add null checks for name and intensity // Add null checks for name and intensity
if (name != null && intensity != null) { if (name != null && intensity != null) {
@ -109,38 +117,24 @@ class _LogsPageState extends State<LogsPage> {
), ),
), ),
), ),
bottomNavigationBar: BottomNavigationBar( bottomNavigationBar: BottomBar(
items: const [ currentIndex: appState.currentIndex,
BottomNavigationBarItem(
icon: Icon(Icons.home),
label: 'Home',
),
BottomNavigationBarItem(
icon: Icon(Icons.settings),
label: 'Settings',
),
BottomNavigationBarItem(
icon: Icon(Icons.list),
label: 'Logs',
),
],
currentIndex: 2, // Set the current index to 2 for the Logs page
onTap: (index) { onTap: (index) {
if (index == 0) { appState.currentIndex = index;
// Navigate back to the main page setState(() {
Navigator.popUntil(context, (route) => route.isFirst); if (index == 0) {
} else if (index == 1) { appState.currentIndex = 0; // Reset to home index
// Navigate to the Settings page // Navigate back to the main page
Navigator.push( Navigator.popUntil(context, (route) => route.isFirst);
context, } else if (index == 1) {
MaterialPageRoute(builder: (context) => const SettingsPage()), // Navigate to the Settings page
); Navigator.push(
} context,
MaterialPageRoute(builder: (context) => const SettingsPage()),
);
}
});
}, },
selectedItemColor: const Color.fromARGB(255, 211, 187, 255), // or any color you prefer
unselectedItemColor: Theme.of(context).textTheme.bodySmall?.color,
showUnselectedLabels: true,
selectedLabelStyle: const TextStyle(fontWeight: FontWeight.bold),
), ),
); );
} }

12
lib/app_state.dart Normal file
View file

@ -0,0 +1,12 @@
import 'package:flutter/material.dart';
class AppState extends ChangeNotifier {
int _currentIndex = 0;
int get currentIndex => _currentIndex;
set currentIndex(int index) {
_currentIndex = index;
notifyListeners();
}
}

38
lib/bottom_bar.dart Normal file
View file

@ -0,0 +1,38 @@
import 'package:flutter/material.dart';
class BottomBar extends StatelessWidget {
final int currentIndex;
final Function(int) onTap;
const BottomBar({
required this.currentIndex,
required this.onTap,
Key? key,
}) : super(key: key);
@override
Widget build(BuildContext context) {
return BottomNavigationBar(
currentIndex: currentIndex,
onTap: onTap,
items: const [
BottomNavigationBarItem(
icon: Icon(Icons.home),
label: 'Home',
),
BottomNavigationBarItem(
icon: Icon(Icons.settings),
label: 'Settings',
),
BottomNavigationBarItem(
icon: Icon(Icons.list),
label: 'Logs',
),
],
selectedItemColor: const Color.fromARGB(255, 211, 187, 255),
unselectedItemColor: Theme.of(context).textTheme.bodySmall?.color,
showUnselectedLabels: true,
selectedLabelStyle: const TextStyle(fontWeight: FontWeight.bold),
);
}
}

View file

@ -5,9 +5,19 @@ import 'package:OpenshockCompanion/settings_page.dart' show SettingsPage;
import 'package:OpenshockCompanion/LogsPage.dart' show LogsPage; import 'package:OpenshockCompanion/LogsPage.dart' show LogsPage;
import 'package:OpenshockCompanion/api_handler.dart' show sendApiRequest; import 'package:OpenshockCompanion/api_handler.dart' show sendApiRequest;
import 'package:shared_preferences/shared_preferences.dart'; import 'package:shared_preferences/shared_preferences.dart';
import 'bottom_bar.dart';
import 'app_state.dart';
import 'package:provider/provider.dart';
void main() { void main() {
runApp(const MyApp()); runApp(
MultiProvider(
providers: [
ChangeNotifierProvider(create: (_) => AppState()), // Add this line
],
child: const MyApp(),
),
);
} }
class MyApp extends StatelessWidget { class MyApp extends StatelessWidget {
@ -34,6 +44,12 @@ class MyApp extends StatelessWidget {
final SharedPreferences prefs = await SharedPreferences.getInstance(); final SharedPreferences prefs = await SharedPreferences.getInstance();
return prefs.getBool('isDarkMode') ?? false; return prefs.getBool('isDarkMode') ?? false;
} }
Future<void> getLimits() async {
final SharedPreferences prefs = await SharedPreferences.getInstance();
final String intensityLimit = prefs.getString('intensityLimit') ?? '';
final String durationLimit = prefs.getString('durationLimit') ?? '';
}
} }
class SliderPage extends StatefulWidget { class SliderPage extends StatefulWidget {
@ -44,12 +60,13 @@ class SliderPage extends StatefulWidget {
} }
class _SliderPageState extends State<SliderPage> { class _SliderPageState extends State<SliderPage> {
int intensityValue = 0; int intensityValue = 1;
int timeValue = 0; int timeValue = 1;
int currentIndex = 0;
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final appState = Provider.of<AppState>(context, listen: false);
return Scaffold( return Scaffold(
appBar: AppBar( appBar: AppBar(
title: const Text('Openshock Companion'), title: const Text('Openshock Companion'),
@ -64,7 +81,7 @@ class _SliderPageState extends State<SliderPage> {
), ),
Slider( Slider(
value: intensityValue.toDouble(), value: intensityValue.toDouble(),
min: 0, min: 1,
max: 100, max: 100,
onChanged: (value) { onChanged: (value) {
setState(() { setState(() {
@ -78,7 +95,7 @@ class _SliderPageState extends State<SliderPage> {
), ),
Slider( Slider(
value: timeValue.toDouble(), value: timeValue.toDouble(),
min: 0, min: 1,
max: 30, max: 30,
onChanged: (value) { onChanged: (value) {
setState(() { setState(() {
@ -129,12 +146,13 @@ class _SliderPageState extends State<SliderPage> {
], ],
), ),
), ),
bottomNavigationBar: BottomNavigationBar( bottomNavigationBar: BottomBar(
currentIndex: currentIndex, currentIndex: appState.currentIndex,
onTap: (index) { onTap: (index) {
appState.currentIndex = index;
setState(() { setState(() {
currentIndex = index;
if (index == 0) { if (index == 0) {
appState.currentIndex = 0; // Reset to home index
// Home tab // Home tab
} else if (index == 1) { } else if (index == 1) {
// Settings tab // Settings tab
@ -150,24 +168,6 @@ class _SliderPageState extends State<SliderPage> {
} }
}); });
}, },
items: const [
BottomNavigationBarItem(
icon: Icon(Icons.home),
label: 'Home',
),
BottomNavigationBarItem(
icon: Icon(Icons.settings),
label: 'Settings',
),
BottomNavigationBarItem(
icon: Icon(Icons.list),
label: 'Logs',
),
],
selectedItemColor: const Color.fromARGB(255, 211, 187, 255), // or any color you prefer
unselectedItemColor: Theme.of(context).textTheme.bodySmall?.color,
showUnselectedLabels: true,
selectedLabelStyle: const TextStyle(fontWeight: FontWeight.bold),
), ),
); );
} }

View file

@ -1,6 +1,11 @@
import 'package:OpenshockCompanion/LogsPage.dart'; // ignore_for_file: use_key_in_widget_constructors, library_private_types_in_public_api, use_build_context_synchronously
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:shared_preferences/shared_preferences.dart'; import 'package:shared_preferences/shared_preferences.dart';
import 'bottom_bar.dart';
import 'app_state.dart'; // Import the AppState class
import 'LogsPage.dart';
import 'package:provider/provider.dart';
class SettingsPage extends StatefulWidget { class SettingsPage extends StatefulWidget {
const SettingsPage({Key? key}); const SettingsPage({Key? key});
@ -12,10 +17,12 @@ class SettingsPage extends StatefulWidget {
class _SettingsPageState extends State<SettingsPage> { class _SettingsPageState extends State<SettingsPage> {
final TextEditingController apiKeyController = TextEditingController(); final TextEditingController apiKeyController = TextEditingController();
final TextEditingController shockerIdController = TextEditingController(); final TextEditingController shockerIdController = TextEditingController();
final TextEditingController intensityLimitController =
TextEditingController();
final TextEditingController durationLimitController = TextEditingController();
bool showApiKey = false; bool showApiKey = false;
bool showShockerId = false; bool showShockerId = false;
bool isDarkMode = false;
@override @override
void initState() { void initState() {
@ -28,20 +35,26 @@ class _SettingsPageState extends State<SettingsPage> {
setState(() { setState(() {
apiKeyController.text = prefs.getString('apiKey') ?? ''; apiKeyController.text = prefs.getString('apiKey') ?? '';
shockerIdController.text = prefs.getString('shockerId') ?? ''; shockerIdController.text = prefs.getString('shockerId') ?? '';
isDarkMode = prefs.getBool('isDarkMode') ?? false; intensityLimitController.text = prefs.getString('intensityLimit') ?? '';
durationLimitController.text = prefs.getString('durationLimit') ?? '';
}); });
} }
Future<void> saveSettingsAndTheme() async { Future<void> saveSettings() async {
final SharedPreferences prefs = await SharedPreferences.getInstance(); final SharedPreferences prefs = await SharedPreferences.getInstance();
prefs.setString('apiKey', apiKeyController.text); prefs.setString('apiKey', apiKeyController.text);
prefs.setString('shockerId', shockerIdController.text); prefs.setString('shockerId', shockerIdController.text);
prefs.setBool('isDarkMode', isDarkMode); intensityLimitController.text =
prefs.getString('intensityLimit') ?? '100'; // Default to 100
durationLimitController.text =
prefs.getString('durationLimit') ?? '30'; // Default to 30
Navigator.pop(context); Navigator.pop(context);
} }
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final appState = Provider.of<AppState>(context, listen: false);
return Scaffold( return Scaffold(
appBar: AppBar( appBar: AppBar(
title: const Text('Settings'), title: const Text('Settings'),
@ -57,7 +70,8 @@ class _SettingsPageState extends State<SettingsPage> {
decoration: InputDecoration( decoration: InputDecoration(
labelText: 'API Key', labelText: 'API Key',
suffixIcon: IconButton( suffixIcon: IconButton(
icon: Icon(showApiKey ? Icons.visibility_off : Icons.visibility), icon: Icon(
showApiKey ? Icons.visibility_off : Icons.visibility),
onPressed: () { onPressed: () {
setState(() { setState(() {
showApiKey = !showApiKey; showApiKey = !showApiKey;
@ -74,7 +88,8 @@ class _SettingsPageState extends State<SettingsPage> {
decoration: InputDecoration( decoration: InputDecoration(
labelText: 'Shocker ID', labelText: 'Shocker ID',
suffixIcon: IconButton( suffixIcon: IconButton(
icon: Icon(showShockerId ? Icons.visibility_off : Icons.visibility), icon: Icon(
showShockerId ? Icons.visibility_off : Icons.visibility),
onPressed: () { onPressed: () {
setState(() { setState(() {
showShockerId = !showShockerId; showShockerId = !showShockerId;
@ -85,58 +100,45 @@ class _SettingsPageState extends State<SettingsPage> {
obscureText: !showShockerId, obscureText: !showShockerId,
), ),
const SizedBox(height: 16), const SizedBox(height: 16),
Row( const Text('Intensity Limit'),
children: [ TextField(
const Text('Dark Mode'), controller: intensityLimitController,
Switch( decoration: const InputDecoration(
value: isDarkMode, labelText: 'Intensity Limit',
onChanged: (value) { ),
setState(() { ),
isDarkMode = value; const SizedBox(height: 16),
}); const Text('Duration Limit'),
}, TextField(
), controller: durationLimitController,
], decoration: const InputDecoration(
labelText: 'Duration Limit',
),
), ),
const SizedBox(height: 16), const SizedBox(height: 16),
ElevatedButton( ElevatedButton(
onPressed: saveSettingsAndTheme, onPressed: saveSettings,
child: const Text('Save'), child: const Text('Save'),
), ),
], ],
), ),
), ),
bottomNavigationBar: BottomNavigationBar( bottomNavigationBar: BottomBar(
items: const [ currentIndex: appState.currentIndex,
BottomNavigationBarItem(
icon: Icon(Icons.home),
label: 'Home',
),
BottomNavigationBarItem(
icon: Icon(Icons.settings),
label: 'Settings',
),
BottomNavigationBarItem(
icon: Icon(Icons.list),
label: 'Logs',
),
],
currentIndex: 1, // Set the current index to 1 for the Settings page
onTap: (index) { onTap: (index) {
if (index == 0) { appState.currentIndex = index;
// Navigate back to the main page setState(() {
Navigator.popUntil(context, (route) => route.isFirst); if (index == 0) {
} else if (index == 2) { appState.currentIndex = 0;
Navigator.push( Navigator.popUntil(context, (route) => route.isFirst);
context, } else if (index == 2) {
MaterialPageRoute(builder: (context) => const LogsPage()), Navigator.push(
); context,
} MaterialPageRoute(builder: (context) => const LogsPage()),
);
}
});
}, },
selectedItemColor: const Color.fromARGB(255, 211, 187, 255), // or any color you prefer
unselectedItemColor: Theme.of(context).textTheme.caption?.color,
showUnselectedLabels: true,
selectedLabelStyle: TextStyle(fontWeight: FontWeight.bold),
), ),
); );
} }

View file

@ -208,6 +208,14 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.9.1" version: "1.9.1"
nested:
dependency: transitive
description:
name: nested
sha256: "03bac4c528c64c95c722ec99280375a6f2fc708eec17c7b3f07253b626cd2a20"
url: "https://pub.dev"
source: hosted
version: "1.0.0"
path: path:
dependency: transitive dependency: transitive
description: description:
@ -272,6 +280,14 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "3.7.3" version: "3.7.3"
provider:
dependency: "direct main"
description:
name: provider
sha256: "9a96a0a19b594dbc5bf0f1f27d2bc67d5f95957359b461cd9feb44ed6ae75096"
url: "https://pub.dev"
source: hosted
version: "6.1.1"
shared_preferences: shared_preferences:
dependency: "direct main" dependency: "direct main"
description: description:

View file

@ -2,9 +2,9 @@ name: OpenshockCompanion
description: Companion app for managing openshock-compatible devices description: Companion app for managing openshock-compatible devices
# The following line prevents the package from being accidentally published to # The following line prevents the package from being accidentally published to
# pub.dev using `flutter pub publish`. This is preferred for private packages. # pub.dev using `flutter pub publish`. This is preferred for private packages.
publish_to: 'none' # Remove this line if you wish to publish to pub.dev publish_to: 'none'
version: 0.2.0 version: 0.2.2
environment: environment:
sdk: '>=3.1.2 <4.0.0' sdk: '>=3.1.2 <4.0.0'
@ -22,6 +22,8 @@ dependencies:
shared_preferences: ^2.0.8 shared_preferences: ^2.0.8
flutter_launcher_icons: ^0.9.2 flutter_launcher_icons: ^0.9.2
fluttertoast: ^8.2.3 fluttertoast: ^8.2.3
provider: ^6.1.1
# The following adds the Cupertino Icons font to your application. # The following adds the Cupertino Icons font to your application.
# Use with the CupertinoIcons class for iOS style icons. # Use with the CupertinoIcons class for iOS style icons.