reworked api handling, portrait behavior
This commit is contained in:
parent
3f112a857b
commit
8cad1e26df
266
lib/NewShareLinkPage.dart
Normal file
266
lib/NewShareLinkPage.dart
Normal file
|
@ -0,0 +1,266 @@
|
|||
import 'dart:convert';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:http/http.dart' as http;
|
||||
import 'package:intl/intl.dart';
|
||||
import 'package:shared_preferences/shared_preferences.dart';
|
||||
|
||||
class NewShareLinkPage extends StatefulWidget {
|
||||
const NewShareLinkPage({Key? key}) : super(key: key);
|
||||
|
||||
@override
|
||||
_NewShareLinkPageState createState() => _NewShareLinkPageState();
|
||||
}
|
||||
|
||||
class _NewShareLinkPageState extends State<NewShareLinkPage> {
|
||||
late TextEditingController linkNameController;
|
||||
DateTime? selectedDateTime;
|
||||
bool neverExpire = false;
|
||||
double durationValue = 0;
|
||||
double intensityValue = 0;
|
||||
String? shareLink;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
linkNameController = TextEditingController();
|
||||
}
|
||||
|
||||
Future<String?> _getApiKey() async {
|
||||
final SharedPreferences prefs = await SharedPreferences.getInstance();
|
||||
return prefs.getString('apiKey');
|
||||
}
|
||||
|
||||
Future<String?> _getShockerId() async {
|
||||
final SharedPreferences prefs = await SharedPreferences.getInstance();
|
||||
return prefs.getString('shockerId');
|
||||
}
|
||||
|
||||
Future<void> saveSettings() async {
|
||||
final SharedPreferences prefs = await SharedPreferences.getInstance();
|
||||
final String? apiKey = prefs.getString('apiKey');
|
||||
final String? shockerId = prefs.getString('shockerId');
|
||||
|
||||
if (apiKey != null && shockerId != null) {
|
||||
final String linkName = linkNameController.text;
|
||||
final int durationInMilliseconds = (durationValue * 1000).toInt();
|
||||
|
||||
final createLinkResponse = await http.post(
|
||||
Uri.parse('https://api.shocklink.net/1/shares/links'),
|
||||
headers: {
|
||||
'accept': 'application/json',
|
||||
'OpenShockToken': apiKey,
|
||||
},
|
||||
body: jsonEncode({
|
||||
'name': linkName,
|
||||
'expiresOn': selectedDateTime?.toIso8601String(),
|
||||
}),
|
||||
);
|
||||
|
||||
if (createLinkResponse.statusCode == 200) {
|
||||
final responseData = jsonDecode(createLinkResponse.body);
|
||||
final String linkId = responseData['data'];
|
||||
|
||||
final addShockerResponse = await http.put(
|
||||
Uri.parse('https://api.shocklink.net/1/shares/links/$linkId/$shockerId'),
|
||||
headers: {
|
||||
'accept': 'application/json',
|
||||
'OpenShockToken': apiKey,
|
||||
},
|
||||
);
|
||||
|
||||
if (addShockerResponse.statusCode == 200) {
|
||||
final setPermissionsResponse = await http.patch(
|
||||
Uri.parse('https://api.shocklink.net/1/shares/links/$linkId/$shockerId'),
|
||||
headers: {
|
||||
'accept': 'application/json',
|
||||
'OpenShockToken': apiKey,
|
||||
},
|
||||
body: jsonEncode({
|
||||
'permissions': {
|
||||
'vibrate': true,
|
||||
'sound': false,
|
||||
'shock': true,
|
||||
},
|
||||
'limits': {
|
||||
'intensity': intensityValue,
|
||||
'duration': durationInMilliseconds,
|
||||
},
|
||||
'cooldown': 30000,
|
||||
}),
|
||||
);
|
||||
|
||||
if (setPermissionsResponse.statusCode == 200) {
|
||||
setState(() {
|
||||
shareLink = 'https://shockl.ink/s/$linkId';
|
||||
});
|
||||
} else {
|
||||
// Handle failure in setting permissions
|
||||
}
|
||||
} else {
|
||||
// Handle failure in adding shocker to the link
|
||||
}
|
||||
} else {
|
||||
// Handle failure in creating the share link
|
||||
}
|
||||
} else {
|
||||
// Handle missing apiKey or shockerId
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _selectDate(BuildContext context) async {
|
||||
final DateTime? picked = await showDatePicker(
|
||||
context: context,
|
||||
initialDate: selectedDateTime ?? DateTime.now(),
|
||||
firstDate: DateTime.now(),
|
||||
lastDate: DateTime(DateTime.now().year + 1),
|
||||
);
|
||||
if (picked != null) {
|
||||
setState(() {
|
||||
selectedDateTime = DateTime(picked.year, picked.month, picked.day,
|
||||
selectedDateTime?.hour ?? 0, selectedDateTime?.minute ?? 0);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _selectTime(BuildContext context) async {
|
||||
final TimeOfDay? picked = await showTimePicker(
|
||||
context: context,
|
||||
initialTime: TimeOfDay.fromDateTime(selectedDateTime ?? DateTime.now()),
|
||||
);
|
||||
if (picked != null) {
|
||||
setState(() {
|
||||
selectedDateTime = DateTime(
|
||||
selectedDateTime?.year ?? DateTime.now().year,
|
||||
selectedDateTime?.month ?? DateTime.now().month,
|
||||
selectedDateTime?.day ?? DateTime.now().day,
|
||||
picked.hour,
|
||||
picked.minute,
|
||||
);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
title: const Text('New Share Link'),
|
||||
),
|
||||
body: Padding(
|
||||
padding: const EdgeInsets.all(16.0),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
const Text('Link Name'),
|
||||
TextFormField(
|
||||
controller: linkNameController,
|
||||
decoration: InputDecoration(
|
||||
hintText: 'Enter link name',
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 20),
|
||||
Row(
|
||||
children: [
|
||||
Checkbox(
|
||||
value: neverExpire,
|
||||
onChanged: (value) {
|
||||
setState(() {
|
||||
neverExpire = value!;
|
||||
if (neverExpire) {
|
||||
selectedDateTime = null;
|
||||
}
|
||||
});
|
||||
},
|
||||
),
|
||||
const Text('Never Expire'),
|
||||
],
|
||||
),
|
||||
if (!neverExpire)
|
||||
Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
const Text('Select Date and Time'),
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
TextButton(
|
||||
onPressed: () => _selectDate(context),
|
||||
child: const Text('Select Date'),
|
||||
),
|
||||
TextButton(
|
||||
onPressed: () => _selectTime(context),
|
||||
child: const Text('Select Time'),
|
||||
),
|
||||
if (selectedDateTime != null)
|
||||
Text(
|
||||
'Selected: ${DateFormat.yMd().add_jm().format(selectedDateTime!)}',
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 20),
|
||||
const Text('Duration'),
|
||||
Slider(
|
||||
value: durationValue,
|
||||
min: 0,
|
||||
max: 30,
|
||||
divisions: 30,
|
||||
label: durationValue.round().toString(),
|
||||
onChanged: (value) {
|
||||
setState(() {
|
||||
durationValue = value;
|
||||
});
|
||||
},
|
||||
),
|
||||
Text('Selected Duration: ${durationValue.round()}'),
|
||||
const SizedBox(height: 20),
|
||||
const Text('Intensity'),
|
||||
Slider(
|
||||
value: intensityValue,
|
||||
min: 0,
|
||||
max: 100,
|
||||
divisions: 100,
|
||||
label: intensityValue.round().toString(),
|
||||
onChanged: (value) {
|
||||
setState(() {
|
||||
intensityValue = value;
|
||||
});
|
||||
},
|
||||
),
|
||||
Text('Selected Intensity: ${intensityValue.round()}'),
|
||||
const SizedBox(height: 20),
|
||||
ElevatedButton(
|
||||
onPressed: () {
|
||||
saveSettings();
|
||||
},
|
||||
child: const Text('Create Share Link'),
|
||||
),
|
||||
if (shareLink != null)
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(vertical: 20),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
const Text(
|
||||
'Generated Share Link:',
|
||||
style: TextStyle(fontWeight: FontWeight.bold),
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
TextFormField(
|
||||
initialValue: shareLink,
|
||||
readOnly: true,
|
||||
decoration: InputDecoration(
|
||||
hintText: 'Generated Share Link',
|
||||
border: OutlineInputBorder(),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
|
@ -49,7 +49,7 @@ Future<void> saveSettings(String apiKey, String shockerId) async {
|
|||
await prefs.setString('shockerId', shockerId);
|
||||
}
|
||||
|
||||
Future<void> sendApiRequest(int intensity, int time, int type) async {
|
||||
Future<bool> sendApiRequest(int intensity, int time, int type) async {
|
||||
// Fetch saved information from SharedPreferences
|
||||
final SharedPreferences prefs = await SharedPreferences.getInstance();
|
||||
|
||||
|
@ -82,13 +82,8 @@ Future<void> sendApiRequest(int intensity, int time, int type) async {
|
|||
);
|
||||
|
||||
if (response.statusCode == 200) {
|
||||
// Request successful, handle the response if needed
|
||||
print('API request successful');
|
||||
print(response.body);
|
||||
return true;
|
||||
} else {
|
||||
// Request failed, handle the error
|
||||
print('API request failed');
|
||||
print('Status code: ${response.statusCode}');
|
||||
print('Response body: ${response.body}');
|
||||
return false;
|
||||
}
|
||||
}
|
|
@ -60,7 +60,6 @@ class _LogsPageState extends State<logs_page> {
|
|||
}
|
||||
|
||||
Icon getIconForType(String type) {
|
||||
print('Type: $type');
|
||||
switch (type.toLowerCase()) {
|
||||
case 'vibrate':
|
||||
return const Icon(Icons.vibration);
|
||||
|
@ -82,26 +81,47 @@ class _LogsPageState extends State<logs_page> {
|
|||
Widget build(BuildContext context) {
|
||||
final appState = Provider.of<AppState>(context, listen: false);
|
||||
appState.currentIndex = 2;
|
||||
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
title: const Text('Logs'),
|
||||
),
|
||||
body: RefreshIndicator(
|
||||
body: LayoutBuilder(
|
||||
builder: (context, constraints) {
|
||||
final isLandscape =
|
||||
MediaQuery.of(context).orientation == Orientation.landscape;
|
||||
|
||||
return RefreshIndicator(
|
||||
onRefresh: _handleRefresh,
|
||||
child: SingleChildScrollView(
|
||||
scrollDirection: Axis.horizontal,
|
||||
child: SingleChildScrollView(
|
||||
child: DataTable(
|
||||
columnSpacing: 10,
|
||||
dataRowMaxHeight: 50,
|
||||
columns: const [
|
||||
DataColumn(label: Text('Name')),
|
||||
DataColumn(label: Text('Intensity')),
|
||||
DataColumn(label: Text('Duration')),
|
||||
DataColumn(label: Text('Type')),
|
||||
DataColumn(label: Text('Time')),
|
||||
columns: [
|
||||
DataColumn(label: SizedBox(
|
||||
width: isLandscape ? constraints.maxWidth * 0.25 : null,
|
||||
child: Text('Name'),
|
||||
)),
|
||||
DataColumn(label: SizedBox(
|
||||
width: isLandscape ? constraints.maxWidth * 0.15 : null,
|
||||
child: Text('Intensity'),
|
||||
)),
|
||||
DataColumn(label: SizedBox(
|
||||
width: isLandscape ? constraints.maxWidth * 0.15
|
||||
: null,
|
||||
child: Text('Duration'),
|
||||
)),
|
||||
DataColumn(label: SizedBox(
|
||||
width: isLandscape ? constraints.maxWidth * 0.15 : null,
|
||||
child: Text('Type'),
|
||||
)),
|
||||
DataColumn(label: SizedBox(
|
||||
width: isLandscape ? constraints.maxWidth * 0.2 : null,
|
||||
child: Text('Time'),
|
||||
)),
|
||||
],
|
||||
rows: logs.map((log) {
|
||||
rows: logs.map<DataRow>((log) {
|
||||
final controlledBy =
|
||||
log['controlledBy'] as Map<String, dynamic>?;
|
||||
if (controlledBy != null) {
|
||||
|
@ -111,16 +131,16 @@ class _LogsPageState extends State<logs_page> {
|
|||
final type = log['type'] as String?;
|
||||
final createdAt = log['createdOn'] as String?;
|
||||
|
||||
if (intensity != null && type != null && createdAt != null) {
|
||||
final userTimezone =
|
||||
DateTime.now().timeZoneOffset; // Get user's timezone
|
||||
|
||||
if (intensity != null &&
|
||||
type != null &&
|
||||
createdAt != null) {
|
||||
final userTimezone = DateTime.now().timeZoneOffset;
|
||||
final utcDateTime = DateTime.parse(createdAt);
|
||||
final localDateTime = utcDateTime
|
||||
.add(userTimezone); // Convert to local timezone
|
||||
final localDateTime =
|
||||
utcDateTime.add(userTimezone);
|
||||
|
||||
final formattedCreatedAt =
|
||||
DateFormat('dd/MM/yy - HH:mm').format(localDateTime);
|
||||
final formattedCreatedAt = DateFormat('dd/MM/yy - HH:mm')
|
||||
.format(localDateTime);
|
||||
|
||||
return DataRow(
|
||||
cells: [
|
||||
|
@ -133,11 +153,13 @@ class _LogsPageState extends State<logs_page> {
|
|||
);
|
||||
}
|
||||
}
|
||||
return const DataRow(cells: []);
|
||||
return DataRow(cells: []);
|
||||
}).toList(),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
bottomNavigationBar: BottomBar(
|
||||
currentIndex: appState.currentIndex,
|
||||
|
|
|
@ -111,26 +111,35 @@ class _SliderPageState extends State<SliderPage> {
|
|||
ElevatedButton.icon(
|
||||
icon: const Icon(Icons.flash_on),
|
||||
label: const Text('Shock'),
|
||||
onPressed: () {
|
||||
onPressed: () async {
|
||||
if (intensityValue < 1 || timeValue < 1) {
|
||||
// this whole thing was written by a silly little cat :3
|
||||
} else {
|
||||
HapticFeedback.vibrate();
|
||||
sendApiRequest(intensityValue, timeValue, 1);
|
||||
showToast('API request sent');
|
||||
bool success = await sendApiRequest(
|
||||
intensityValue, timeValue, 1);
|
||||
if (success) {
|
||||
showToast('Shock API request successful');
|
||||
} else {
|
||||
showToast('Failed to send Shock API request');
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
),
|
||||
const SizedBox(width: 8.0),
|
||||
ElevatedButton.icon(
|
||||
icon: const Icon(Icons.vibration),
|
||||
label: const Text('Vibrate'),
|
||||
onPressed: () {
|
||||
onPressed: () async {
|
||||
if (intensityValue < 1 || timeValue < 1) {
|
||||
} else {
|
||||
HapticFeedback.vibrate();
|
||||
sendApiRequest(intensityValue, timeValue, 2);
|
||||
showToast('API request sent');
|
||||
bool success = await sendApiRequest(intensityValue, timeValue, 2);
|
||||
if (success) {
|
||||
showToast('Vibrate API request successful');
|
||||
} else {
|
||||
showToast('Failed to send Vibrate API request');
|
||||
}
|
||||
}
|
||||
},
|
||||
),
|
||||
|
|
|
@ -7,6 +7,7 @@ import 'logs_page.dart';
|
|||
import 'package:provider/provider.dart';
|
||||
import 'package:http/http.dart' as http;
|
||||
import 'package:fluttertoast/fluttertoast.dart';
|
||||
import 'NewShareLinkPage.dart';
|
||||
|
||||
class settings_page extends StatefulWidget {
|
||||
const settings_page({Key? key});
|
||||
|
@ -39,8 +40,6 @@ class _SettingsPageState extends State<settings_page> {
|
|||
setState(() {
|
||||
apiKeyController.text = prefs.getString('apiKey') ?? '';
|
||||
shockerIdController.text = prefs.getString('shockerId') ?? '';
|
||||
intensityLimitController.text = prefs.getString('intensityLimit') ?? '';
|
||||
durationLimitController.text = prefs.getString('durationLimit') ?? '';
|
||||
numberOfLogs = prefs.getDouble(logsSharedPreferenceKey) ?? 30;
|
||||
});
|
||||
}
|
||||
|
@ -50,8 +49,6 @@ class _SettingsPageState extends State<settings_page> {
|
|||
prefs.setString('apiKey', apiKeyController.text);
|
||||
prefs.setString('shockerId', shockerIdController.text);
|
||||
await runChecks();
|
||||
|
||||
// Save the selected number of logs to shared preferences
|
||||
prefs.setDouble(logsSharedPreferenceKey, numberOfLogs);
|
||||
|
||||
Navigator.pop(context);
|
||||
|
@ -135,7 +132,8 @@ class _SettingsPageState extends State<settings_page> {
|
|||
appBar: AppBar(
|
||||
title: const Text('Settings'),
|
||||
),
|
||||
body: Padding(
|
||||
body: SingleChildScrollView(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(16.0),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
|
@ -199,6 +197,26 @@ class _SettingsPageState extends State<settings_page> {
|
|||
child: const Text('Save'),
|
||||
),
|
||||
const SizedBox(height: 15),
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
ElevatedButton(
|
||||
onPressed: () {
|
||||
Navigator.push(
|
||||
context,
|
||||
MaterialPageRoute(builder: (context) => const NewShareLinkPage()),
|
||||
);
|
||||
},
|
||||
child: const Text('New Share Link'),
|
||||
),
|
||||
ElevatedButton(
|
||||
onPressed: () {
|
||||
|
||||
},
|
||||
child: const Text('My Share Links'),
|
||||
),
|
||||
],
|
||||
),
|
||||
FutureBuilder<String>(
|
||||
future: fetchCommitData(),
|
||||
builder: (context, snapshot) {
|
||||
|
@ -208,7 +226,7 @@ class _SettingsPageState extends State<settings_page> {
|
|||
return Text('Error: ${snapshot.error}');
|
||||
} else {
|
||||
return Text(
|
||||
'App Version: 0.3-rc0[hf] - Build Date: Dec. 11, 2023\n'
|
||||
'App Version: 0.3-rc3 - Build Date: Dec. 11, 2023\n'
|
||||
'(C) Mercury, 2023\n'
|
||||
'Connected to api.shocklink.org, version ${snapshot.data}',
|
||||
textAlign: TextAlign.left,
|
||||
|
@ -220,6 +238,7 @@ class _SettingsPageState extends State<settings_page> {
|
|||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
bottomNavigationBar: BottomBar(
|
||||
currentIndex: appState.currentIndex,
|
||||
onTap: (index) {
|
||||
|
|
Loading…
Reference in a new issue