diff --git a/README.md b/README.md index 556dccf..1f89435 100644 --- a/README.md +++ b/README.md @@ -9,6 +9,14 @@ This is a simple Flutter/Dart-based mobile app: You touch the screen and the lig ## Why another flashlight app? As unbelievable as it might seem, this, to our knowledge, is the first app that enables your device's flashlight as long as your finger is in contact with the screen. +## Download + +[Get it on F-Droid](https://f-droid.org/packages/com.softcodingforyou.touchtorch/) + +Or download the latest APK from the [Releases Section](https://github.com/SoftcodingForYou/touchtorch/releases/latest). + ## Code structure The whole app is written with less than 100 lines of Flutter/Dart code. diff --git a/lib/home.dart b/lib/home.dart index 3385cf9..19d0a63 100644 --- a/lib/home.dart +++ b/lib/home.dart @@ -2,74 +2,93 @@ import 'package:flutter/material.dart'; import 'package:touchtorch/fallback_screen.dart'; import 'package:torch_light/torch_light.dart'; -class Home extends StatelessWidget { +class Home extends StatefulWidget { const Home({super.key}); + @override + State createState() => _HomeState(); +} + +class _HomeState extends State { + bool _isTorchOn = false; + @override Widget build(BuildContext context) { return Scaffold( body: FutureBuilder( future: _isTorchAvailable(context), - builder: (BuildContext context, AsyncSnapshot snapshot) { + builder: (context, snapshot) { if (snapshot.hasData && snapshot.data!) { - return Center( - child: Container( - color: Colors.blueGrey.shade900, - height: MediaQuery.of(context).size.height, - width: MediaQuery.of(context).size.width, - child: Stack(alignment: Alignment.center, children: [ - Positioned( - top: 35, - child: Row( - crossAxisAlignment: CrossAxisAlignment.start, - mainAxisAlignment: MainAxisAlignment.center, + return GestureDetector( + onTapDown: (_) => _toggleTorch(true), + onTapUp: (_) => _toggleTorch(false), + onPanEnd: (_) => _toggleTorch(false), + onPanCancel: () => _toggleTorch(false), + child: AnimatedContainer( + duration: const Duration(milliseconds: 150), + color: _isTorchOn + ? Colors.yellow.shade700 + : Colors.blueGrey.shade900, + width: double.infinity, + height: double.infinity, + child: SafeArea( + child: Padding( + padding: const EdgeInsets.all(20.0), + child: Stack( + alignment: Alignment.center, children: [ - Text( - "Flashlight stays on as long\nas you touch the screen", - textAlign: TextAlign.end, - style: TextStyle(color: Colors.blueGrey.shade500)), - ]), - ), - Image.asset( - "assets/images/morse_alphabet.png", - cacheWidth: - (MediaQuery.of(context).devicePixelRatio * 200).round(), - color: Colors.blueGrey.shade800, - ), - Positioned( - right: 15, - bottom: 15, - child: Image.asset( - "assets/images/click_icon.png", - cacheWidth: - (MediaQuery.of(context).devicePixelRatio * 35).round(), - width: 35, - color: Colors.white, + Column( + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + const SizedBox(height: 20), + Text( + "Touch and hold the screen\nto turn on the flashlight", + textAlign: TextAlign.center, + style: TextStyle( + fontSize: 18, + color: Colors.blueGrey.shade200, + fontWeight: FontWeight.w500, + ), + ), + const SizedBox(height: 20), + Image.asset( + "assets/images/morse_alphabet.png", + cacheWidth: (MediaQuery.of(context) + .devicePixelRatio * + 200) + .round(), + color: Colors.blueGrey.shade800, + ), + ], + ), + Positioned( + right: 15, + bottom: 15, + child: Image.asset( + "assets/images/click_icon.png", + cacheWidth: (MediaQuery.of(context) + .devicePixelRatio * + 35) + .round(), + width: 35, + color: Colors.white, + ), + ), + ], + ), ), ), - GestureDetector( - onTapDown: (details) async { - _enableTorch(context); - }, - onTapUp: (details) async { - _disableTorch(context); - }, - onPanEnd: (details) async { - _disableTorch(context); - }, - onPanCancel: () async { - _disableTorch(context); - }, - ), - ]), - )); + ), + ); } else if (snapshot.hasData) { return const FallbackScreen( - fallbackText: "Device's torch is not available."); + fallbackText: "Device's torch is not available.", + ); } else { return const FallbackScreen( - fallbackText: - "An unhandled situation occurred\n\nPlease, contact the developer of this app!"); + fallbackText: + "An unexpected error occurred.\nPlease contact the developer.", + ); } }, ), @@ -77,45 +96,36 @@ class Home extends StatelessWidget { } Future _isTorchAvailable(BuildContext context) async { - final scaffoldMessenger = ScaffoldMessenger.of(context); - try { return await TorchLight.isTorchAvailable(); - } on Exception catch (_) { - scaffoldMessenger.showSnackBar( - const SnackBar( - content: Text('Could not check if the device has an available torch'), - ), - ); + } catch (_) { + _showError("Could not check if the device has a torch."); rethrow; } } - Future _enableTorch(BuildContext context) async { - final scaffoldMessenger = ScaffoldMessenger.of(context); + Future _toggleTorch(bool turnOn) async { + if (_isTorchOn == turnOn) return; + + setState(() { + _isTorchOn = turnOn; + }); try { - await TorchLight.enableTorch(); - } on Exception catch (_) { - scaffoldMessenger.showSnackBar( - const SnackBar( - content: Text('Could not enable torch'), - ), - ); + if (turnOn) { + await TorchLight.enableTorch(); + } else { + await TorchLight.disableTorch(); + } + } catch (_) { + _showError("Failed to ${turnOn ? "enable" : "disable"} torch."); } } - Future _disableTorch(BuildContext context) async { - final scaffoldMessenger = ScaffoldMessenger.of(context); - - try { - await TorchLight.disableTorch(); - } on Exception catch (_) { - scaffoldMessenger.showSnackBar( - const SnackBar( - content: Text('Could not disable torch'), - ), - ); - } + void _showError(String message) { + ScaffoldMessenger.of(context).showSnackBar( + SnackBar(content: Text(message)), + ); } } +