@@ -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\n Note: 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\n Note: 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\n You 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 (),
0 commit comments