Skip to content

Commit b8db215

Browse files
committed
Add spin the bottle
1 parent cfed340 commit b8db215

File tree

4 files changed

+236
-29
lines changed

4 files changed

+236
-29
lines changed
Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,4 @@
11
**Changelog:**
2-
- Added update check via GitHub api
2+
- Added update check via GitHub api
3+
- Fixed drop down in Random screen not remembering what was selected
4+
- Added Spin the bottle to Random shocks screen (Settings -> More tools -> Random shocks)

lib/screens/tools/bottom/bottom.dart

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import 'package:flutter/material.dart';
1+
import 'package:flutter/material.dart';
22
import 'package:shock_alarm_app/components/constrained_container.dart';
33
import 'package:shock_alarm_app/components/page_padding.dart';
44
import 'package:shock_alarm_app/components/predefined_spacing.dart';

lib/screens/tools/random_shocks/random_shocks.dart

Lines changed: 231 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,21 @@ class _RandomShocksScreenState extends State<RandomShocksScreen> {
3030
DateTime lastRandom = DateTime.now();
3131
DateTime nextRandom = DateTime.now();
3232
List<ControlType> validControlTypes = [];
33+
bool showBottleFlipUi = false;
34+
35+
ControlType getRandomControlType() {
36+
if (validControlTypes.isEmpty) {
37+
return ControlType.shock;
38+
}
39+
return validControlTypes[Random().nextInt(validControlTypes.length)];
40+
}
41+
42+
int getRandomIntensity(ControlType type) {
43+
return AlarmListManager.getInstance().settings.useSeperateSliders &&
44+
type == ControlType.vibrate
45+
? controlsContainer.getRandomVibrateIntensity()
46+
: controlsContainer.getRandomIntensity();
47+
}
3348

3449
void startRandom() async {
3550
running = true;
@@ -51,9 +66,10 @@ class _RandomShocksScreenState extends State<RandomShocksScreen> {
5166
if (runningId != currentId) {
5267
return;
5368
}
54-
ControlType type =
55-
validControlTypes[Random().nextInt(validControlTypes.length)];
56-
executeAll(type, AlarmListManager.getInstance().settings.useSeperateSliders && type == ControlType.vibrate ? controlsContainer.getRandomVibrateIntensity() : controlsContainer.getRandomIntensity(),
69+
ControlType type = getRandomControlType();
70+
executeAll(
71+
type,
72+
getRandomIntensity(type),
5773
controlsContainer.getRandomDuration());
5874
}
5975
}
@@ -73,6 +89,12 @@ class _RandomShocksScreenState extends State<RandomShocksScreen> {
7389
AlarmListManager.getInstance().sendControls(controls);
7490
}
7591

92+
@override
93+
void initState() {
94+
validControlTypes = availableControls.values.first;
95+
super.initState();
96+
}
97+
7698
@override
7799
void dispose() async {
78100
super.dispose();
@@ -95,9 +117,197 @@ class _RandomShocksScreenState extends State<RandomShocksScreen> {
95117
setState(() {});
96118
}
97119

120+
void doSpin() async {
121+
if (AlarmListManager.getInstance().getSelectedShockers().isEmpty) {
122+
ErrorDialog.show("No shockers selected",
123+
"Please select at least one shocker to spin the bottle.");
124+
return;
125+
}
126+
spinText = "";
127+
spinDone = false;
128+
showBottleFlipUi = true;
129+
randomlySelectedShocker = Random()
130+
.nextInt(AlarmListManager.getInstance().getSelectedShockers().length);
131+
angle %= 2 * pi;
132+
int spins = Random().nextInt(5) + 6;
133+
double spinAngle = 2 * pi * spins +
134+
2 *
135+
pi *
136+
(randomlySelectedShocker /
137+
AlarmListManager.getInstance().getSelectedShockers().length);
138+
spinAngle += 2 * pi - angle;
139+
print(spinAngle);
140+
141+
//now spin it over time
142+
double spinDuration = 2000 + Random().nextDouble() * 200;
143+
double startTime = DateTime.now().millisecondsSinceEpoch.toDouble();
144+
double endTime = startTime + spinDuration;
145+
146+
double startAngle = angle;
147+
double progress = 0;
148+
149+
// now animate it
150+
while (progress < 1) {
151+
progress =
152+
(DateTime.now().millisecondsSinceEpoch.toDouble() - startTime) /
153+
(spinDuration);
154+
setState(() {
155+
angle = startAngle + spinAngle * sin(progress*pi/2);
156+
});
157+
await Future.delayed(Duration(milliseconds: 10));
158+
}
159+
ControlType type =
160+
getRandomControlType(); // get the random control type
161+
int intensity = getRandomIntensity(type);
162+
String prefix = "${type.name} @ $intensity for ${(controlsContainer.getRandomDuration()/1000).toStringAsFixed(1)} sec";
163+
setState(() {
164+
angle = startAngle + spinAngle;
165+
spinText =
166+
"$prefix in 3";
167+
spinDone = true;
168+
});
169+
await Future.delayed(Duration(milliseconds: 1000));
170+
setState(() {
171+
spinText = "$prefix in 2";
172+
});
173+
await Future.delayed(Duration(milliseconds: 1000));
174+
setState(() {
175+
spinText = "$prefix in 1";
176+
});
177+
await Future.delayed(Duration(milliseconds: 1000));
178+
setState(() {
179+
spinText = "$prefix now!";
180+
});
181+
Shocker s = AlarmListManager.getInstance()
182+
.getSelectedShockers().elementAt(randomlySelectedShocker);
183+
AlarmListManager.getInstance().sendControls([s.getLimitedControls(type, intensity, controlsContainer.getRandomDuration())]);
184+
await Future.delayed(Duration(milliseconds: 2000));
185+
//now stop it
186+
setState(() {
187+
showBottleFlipUi = false;
188+
});
189+
}
190+
191+
int randomlySelectedShocker = 0;
192+
bool spinDone = false;
193+
String spinText = "";
194+
195+
double angle = 0;
196+
197+
Widget buildSpinningBottle() {
198+
List<Shocker> shockers =
199+
AlarmListManager.getInstance().getSelectedShockers().toList();
200+
201+
ThemeData t = Theme.of(context);
202+
return Scaffold(
203+
appBar: AppBar(
204+
title: Text('Random shocks'),
205+
),
206+
body: PagePadding(
207+
child: ConstrainedContainer(
208+
child: Column(
209+
mainAxisAlignment: MainAxisAlignment.center,
210+
spacing: 10,
211+
children: <Widget>[
212+
213+
Container(height: 40, child: Text(spinText, style: t.textTheme.headlineMedium,),),
214+
Expanded(child: Column(children: [LayoutBuilder(builder: (context, constraints) {
215+
double size = constraints.maxWidth;
216+
if (constraints.maxHeight < constraints.maxWidth) {
217+
size = constraints.maxHeight;
218+
}
219+
220+
return Container(
221+
width: size,
222+
height: size,
223+
decoration: BoxDecoration(
224+
color: t.colorScheme.onSecondary,
225+
shape: BoxShape.circle,
226+
),
227+
child: Stack(
228+
children: [
229+
Positioned(
230+
top: size / 2,
231+
left: size / 2,
232+
child: Center(
233+
child: Transform(
234+
transform: Matrix4.identity()..rotateZ(angle),
235+
child: FractionalTranslation(
236+
translation: Offset(-0.5, -0.5),
237+
child: Row(
238+
mainAxisSize: MainAxisSize.min,
239+
children: [
240+
Text(
241+
"BOTTLE", // spin it
242+
textAlign: TextAlign.center,
243+
style: TextStyle(
244+
fontSize: size / 10,
245+
fontWeight: FontWeight.bold,
246+
color: Theme.of(context)
247+
.colorScheme
248+
.primary,
249+
),
250+
),
251+
Icon(
252+
Icons.arrow_right,
253+
size: size / 7,
254+
)
255+
])))),
256+
),
257+
...shockers.map((shocker) => Positioned(
258+
left: size / 2 +
259+
cos(2 *
260+
pi /
261+
shockers.length *
262+
shockers.indexOf(shocker)) *
263+
(size / 2 - 50),
264+
top: size / 2 +
265+
sin(2 *
266+
pi /
267+
shockers.length *
268+
shockers.indexOf(shocker)) *
269+
(size / 2 - 50),
270+
child: FractionalTranslation(
271+
translation: Offset(-0.5, -0.5),
272+
child: Chip(
273+
label: Text(shocker.name),
274+
backgroundColor: shockers.indexOf(shocker) ==
275+
randomlySelectedShocker &&
276+
spinDone
277+
? t.colorScheme.onPrimary // ToDo: change this color
278+
: null,
279+
)),
280+
)),
281+
],
282+
),
283+
);
284+
})]),),
285+
],
286+
))),
287+
);
288+
}
289+
290+
Map<String, List<ControlType>> availableControls = {
291+
"Shock": [ControlType.shock],
292+
"Vibrate": [ControlType.vibrate],
293+
"Sound": [ControlType.sound],
294+
"Shock & Vibrate": [ControlType.shock, ControlType.vibrate],
295+
"Shock & Sound": [ControlType.shock, ControlType.sound],
296+
"Vibrate & Sound": [ControlType.vibrate, ControlType.sound],
297+
"All": [
298+
ControlType.shock,
299+
ControlType.vibrate,
300+
ControlType.sound
301+
],
302+
};
303+
98304
@override
99305
Widget build(BuildContext context) {
100-
Shocker limitedShocker = AlarmListManager.getInstance().getSelectedShockerLimits();
306+
Shocker limitedShocker =
307+
AlarmListManager.getInstance().getSelectedShockerLimits();
308+
if (showBottleFlipUi) {
309+
return buildSpinningBottle();
310+
}
101311
return Scaffold(
102312
appBar: AppBar(
103313
title: Text('Random shocks'),
@@ -117,15 +327,17 @@ class _RandomShocksScreenState extends State<RandomShocksScreen> {
117327
IconButton(
118328
onPressed: () {
119329
InfoDialog.show("Random shocks",
120-
"This tool will do random shocks with the selected shockers. You can set the intensity and duration range, the delay between the shocks and the type of control to use randomly.\n\nNote: This feature might only work while the app is open in the background. Closing it on android or changing to another window on web may stop this feature temporarely.");
330+
"This tool will do random shocks with the selected shockers. You can set the intensity and duration range, the delay between the shocks and the type of control to use randomly.\n\nNote: This feature might only work while the app is open in the background. Closing it on android or changing to another window on web may stop this feature temporarily.\n\nYou can also spin a bottle by pressing the random button next to the play button");
121331
},
122332
icon: Icon(Icons.info)),
123333
IntensityDurationSelector(
124334
controlsContainer: controlsContainer,
125335
onSet: (ControlsContainer c) {
126336
setState(() {});
127337
},
128-
showSeperateIntensities: AlarmListManager.getInstance().settings.useSeperateSliders,
338+
showSeperateIntensities: AlarmListManager.getInstance()
339+
.settings
340+
.useSeperateSliders,
129341
maxDuration: limitedShocker.durationLimit,
130342
maxIntensity: limitedShocker.intensityLimit,
131343
allowRandom: true,
@@ -159,27 +371,12 @@ class _RandomShocksScreenState extends State<RandomShocksScreen> {
159371
],
160372
),
161373
DropdownMenu<List<ControlType>>(
162-
initialSelection: [ControlType.shock],
163-
dropdownMenuEntries: [
164-
DropdownMenuEntry(value: [ControlType.shock], label: "Shock"),
165-
DropdownMenuEntry(
166-
value: [ControlType.vibrate], label: "Vibrate"),
167-
DropdownMenuEntry(value: [ControlType.sound], label: "Sound"),
168-
DropdownMenuEntry(
169-
value: [ControlType.shock, ControlType.vibrate],
170-
label: "Shock & Vibrate"),
171-
DropdownMenuEntry(
172-
value: [ControlType.shock, ControlType.sound],
173-
label: "Shock & Sound"),
174-
DropdownMenuEntry(
175-
value: [ControlType.vibrate, ControlType.sound],
176-
label: "Vibrate & Sound"),
177-
DropdownMenuEntry(value: [
178-
ControlType.shock,
179-
ControlType.vibrate,
180-
ControlType.sound
181-
], label: "All"),
182-
],
374+
initialSelection: validControlTypes,
375+
dropdownMenuEntries: availableControls.entries.map((entry) =>
376+
DropdownMenuEntry<List<ControlType>>(
377+
value: entry.value,
378+
label: entry.key)
379+
).toList(),
183380
onSelected: (value) {
184381
validControlTypes = value ?? [];
185382
},
@@ -192,6 +389,13 @@ class _RandomShocksScreenState extends State<RandomShocksScreen> {
192389
onPressed: startRandom, icon: Icon(Icons.play_arrow)),
193390
if (running)
194391
IconButton(onPressed: stopRandom, icon: Icon(Icons.stop)),
392+
IconButton(
393+
onPressed: () {
394+
setState(() {
395+
doSpin();
396+
});
397+
},
398+
icon: Icon(Icons.casino))
195399
],
196400
),
197401
PredefinedSpacing(),

lib/services/alarm_list_manager.dart

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -785,6 +785,7 @@ class AlarmListManager {
785785
OpenShockClient client = OpenShockClient();
786786
for (var token in getTokens()) {
787787
if (controlsByToken.containsKey(token.id)) {
788+
print("seinding");
788789
if (!await client.sendControls(token, controlsByToken[token.id]!, this,
789790
customName: customName,
790791
useWs: !settings.useHttpShocking && useWs)) {

0 commit comments

Comments
 (0)