mirror of
https://github.com/Myzel394/quid_faciam_hodie.git
synced 2025-06-18 23:35:25 +02:00
added main screen & login screen
This commit is contained in:
parent
60d830aca4
commit
fbcc2982c6
@ -26,7 +26,7 @@ apply plugin: 'kotlin-android'
|
||||
apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle"
|
||||
|
||||
android {
|
||||
compileSdkVersion flutter.compileSdkVersion
|
||||
compileSdkVersion 33
|
||||
ndkVersion flutter.ndkVersion
|
||||
|
||||
compileOptions {
|
||||
@ -44,10 +44,10 @@ android {
|
||||
|
||||
defaultConfig {
|
||||
// TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html).
|
||||
applicationId "com.example.share_location"
|
||||
applicationId "floss.myzel394.quid_faciam_hodie"
|
||||
// You can update the following values to match your application needs.
|
||||
// For more information, see: https://docs.flutter.dev/deployment/android#reviewing-the-build-configuration.
|
||||
minSdkVersion flutter.minSdkVersion
|
||||
minSdkVersion 21
|
||||
targetSdkVersion flutter.targetSdkVersion
|
||||
versionCode flutterVersionCode.toInteger()
|
||||
versionName flutterVersionName
|
||||
|
@ -23,6 +23,10 @@
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.MAIN"/>
|
||||
<category android:name="android.intent.category.LAUNCHER"/>
|
||||
|
||||
<data
|
||||
android:scheme="floss.myzel394.quid_faciam_hodie"
|
||||
android:host="login-callback" />
|
||||
</intent-filter>
|
||||
</activity>
|
||||
<!-- Don't delete the meta-data below.
|
||||
@ -31,4 +35,6 @@
|
||||
android:name="flutterEmbedding"
|
||||
android:value="2" />
|
||||
</application>
|
||||
|
||||
<uses-permission android:name="android.permission.INTERNET"/>
|
||||
</manifest>
|
||||
|
@ -45,5 +45,31 @@
|
||||
<false/>
|
||||
<key>CADisableMinimumFrameDurationOnPhone</key>
|
||||
<true/>
|
||||
|
||||
<key>NSCameraUsageDescription</key>
|
||||
<string>Taking photos is a crucial part of the app.</string>
|
||||
|
||||
<key>NSMicrophoneUsageDescription</key>
|
||||
<string>Recording videos is a crucial part of the app.</string>
|
||||
|
||||
<key>CFBundleURLTypes</key>
|
||||
<array>
|
||||
<dict>
|
||||
<key>CFBundleTypeRole</key>
|
||||
<string>Editor</string>
|
||||
<key>CFBundleURLSchemes</key>
|
||||
<array>
|
||||
<string>floss.myzel394.quid_faciam_hodie</string>
|
||||
</array>
|
||||
</dict>
|
||||
</array>
|
||||
|
||||
<key>NSAppTransportSecurity</key>
|
||||
<dict>
|
||||
<key>NSAllowsArbitraryLoads</key>
|
||||
<true/>
|
||||
</dict>
|
||||
|
||||
|
||||
</dict>
|
||||
</plist>
|
||||
|
3
lib/constants/apis.dart
Normal file
3
lib/constants/apis.dart
Normal file
@ -0,0 +1,3 @@
|
||||
const SUPABASE_API_URL = 'https://gmqzelvauqziurlloawb.supabase.co';
|
||||
const SUPABASE_API_KEY =
|
||||
'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6ImdtcXplbHZhdXF6aXVybGxvYXdiIiwicm9sZSI6ImFub24iLCJpYXQiOjE2NjAzODE5MDcsImV4cCI6MTk3NTk1NzkwN30.D_964EIlD9WRFnG6MWtQtmIg04eMBbZhIEF7zl--bKw';
|
1
lib/constants/app_values.dart
Normal file
1
lib/constants/app_values.dart
Normal file
@ -0,0 +1 @@
|
||||
const APP_ID = 'floss.myzel394.quid_faciam_hodie';
|
@ -1,6 +1,6 @@
|
||||
const SPACE_MULTIPLIER = 2.0;
|
||||
|
||||
const SMALL_SPACE = SPACE_MULTIPLIER * 1;
|
||||
const SMALL_SPACE = SPACE_MULTIPLIER * 5;
|
||||
const MEDIUM_SPACE = SPACE_MULTIPLIER * 10;
|
||||
const LARGE_SPACE = SPACE_MULTIPLIER * 20;
|
||||
const HUGE_SPACE = SPACE_MULTIPLIER * 40;
|
||||
|
4
lib/enums.dart
Normal file
4
lib/enums.dart
Normal file
@ -0,0 +1,4 @@
|
||||
enum MemoryType {
|
||||
photo,
|
||||
video,
|
||||
}
|
20
lib/extensions/snackbar.dart
Normal file
20
lib/extensions/snackbar.dart
Normal file
@ -0,0 +1,20 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:supabase_flutter/supabase_flutter.dart';
|
||||
|
||||
final supabase = Supabase.instance.client;
|
||||
|
||||
extension ShowSnackBar on BuildContext {
|
||||
void showSnackBar({
|
||||
required String message,
|
||||
Color backgroundColor = Colors.white,
|
||||
}) {
|
||||
ScaffoldMessenger.of(this).showSnackBar(SnackBar(
|
||||
content: Text(message),
|
||||
backgroundColor: backgroundColor,
|
||||
));
|
||||
}
|
||||
|
||||
void showErrorSnackBar({required String message}) {
|
||||
showSnackBar(message: message, backgroundColor: Colors.red);
|
||||
}
|
||||
}
|
3
lib/global_values.dart
Normal file
3
lib/global_values.dart
Normal file
@ -0,0 +1,3 @@
|
||||
import 'package:camera/camera.dart';
|
||||
|
||||
List<CameraDescription> cameras = [];
|
@ -1,9 +1,26 @@
|
||||
import 'package:camera/camera.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:share_location/constants/apis.dart';
|
||||
import 'package:share_location/screens/login_screen.dart';
|
||||
import 'package:share_location/screens/main_screen.dart';
|
||||
import 'package:share_location/screens/welcome_screen.dart';
|
||||
import 'package:supabase_flutter/supabase_flutter.dart';
|
||||
|
||||
import 'managers/global_values_manager.dart';
|
||||
import 'managers/startup_page_manager.dart';
|
||||
|
||||
void main() async {
|
||||
WidgetsFlutterBinding.ensureInitialized();
|
||||
|
||||
await Supabase.initialize(
|
||||
url: SUPABASE_API_URL,
|
||||
anonKey: SUPABASE_API_KEY,
|
||||
debug: kDebugMode,
|
||||
);
|
||||
|
||||
GlobalValuesManager.setCameras(await availableCameras());
|
||||
|
||||
final initialPage = await StartupPageManager.getPage();
|
||||
|
||||
runApp(MyApp(initialPage: initialPage));
|
||||
@ -17,21 +34,30 @@ class MyApp extends StatelessWidget {
|
||||
required this.initialPage,
|
||||
}) : super(key: key);
|
||||
|
||||
// This widget is the root of your application.
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return MaterialApp(
|
||||
title: 'Flutter Demo',
|
||||
theme: ThemeData.dark().copyWith(
|
||||
textTheme: ThemeData.dark().textTheme.copyWith(
|
||||
headline1: TextStyle(
|
||||
headline1: const TextStyle(
|
||||
fontSize: 32,
|
||||
fontWeight: FontWeight.w500,
|
||||
),
|
||||
),
|
||||
inputDecorationTheme: InputDecorationTheme(
|
||||
filled: true,
|
||||
helperMaxLines: 10,
|
||||
errorMaxLines: 10,
|
||||
border: OutlineInputBorder(
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
),
|
||||
),
|
||||
),
|
||||
routes: {
|
||||
WelcomeScreen.ID: (context) => WelcomeScreen(),
|
||||
WelcomeScreen.ID: (context) => const WelcomeScreen(),
|
||||
MainScreen.ID: (context) => const MainScreen(),
|
||||
LoginScreen.ID: (context) => const LoginScreen(),
|
||||
},
|
||||
initialRoute: initialPage,
|
||||
);
|
||||
|
32
lib/managers/authentication_manager.dart
Normal file
32
lib/managers/authentication_manager.dart
Normal file
@ -0,0 +1,32 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:share_location/extensions/snackbar.dart';
|
||||
import 'package:share_location/screens/login_screen.dart';
|
||||
import 'package:share_location/screens/main_screen.dart';
|
||||
import 'package:supabase/supabase.dart';
|
||||
import 'package:supabase_flutter/supabase_flutter.dart';
|
||||
|
||||
class AuthState<T extends StatefulWidget> extends SupabaseAuthState<T> {
|
||||
@override
|
||||
void onUnauthenticated() {
|
||||
if (mounted) {
|
||||
Navigator.of(context)
|
||||
.pushNamedAndRemoveUntil(LoginScreen.ID, (route) => false);
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
void onAuthenticated(Session session) {
|
||||
if (mounted) {
|
||||
Navigator.of(context)
|
||||
.pushNamedAndRemoveUntil(MainScreen.ID, (route) => false);
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
void onPasswordRecovery(Session session) {}
|
||||
|
||||
@override
|
||||
void onErrorAuthenticating(String message) {
|
||||
context.showErrorSnackBar(message: message);
|
||||
}
|
||||
}
|
71
lib/managers/file_manager.dart
Normal file
71
lib/managers/file_manager.dart
Normal file
@ -0,0 +1,71 @@
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:share_location/enums.dart';
|
||||
import 'package:supabase_flutter/supabase_flutter.dart';
|
||||
import 'package:uuid/uuid.dart';
|
||||
|
||||
const uuid = Uuid();
|
||||
|
||||
final supabase = Supabase.instance.client;
|
||||
|
||||
class FileManager {
|
||||
static Future<User> getUser(final String userID) async {
|
||||
final response = await supabase
|
||||
.from('users')
|
||||
.select()
|
||||
.eq('id', userID)
|
||||
.single()
|
||||
.execute();
|
||||
|
||||
return response.data;
|
||||
}
|
||||
|
||||
static uploadFile(final User user, final File file) async {
|
||||
final basename = uuid.v4();
|
||||
final extension = file.path.split('.').last;
|
||||
final filename = '$basename.$extension';
|
||||
final path = '${user.id}/$filename';
|
||||
|
||||
final response = await supabase.storage.from('memories').upload(path, file);
|
||||
|
||||
if (response.error != null) {
|
||||
throw Exception('Error uploading file: ${response.error!.message}');
|
||||
}
|
||||
|
||||
final memoryResponse = await supabase.from('memories').insert({
|
||||
'user': user.id,
|
||||
'location': path,
|
||||
}).execute();
|
||||
|
||||
if (memoryResponse.error != null) {
|
||||
throw Exception('Error creating memory: ${response.error!.message}');
|
||||
}
|
||||
}
|
||||
|
||||
static Future<List?> getLastFile(final User user) async {
|
||||
final response = await supabase
|
||||
.from('memories')
|
||||
.select()
|
||||
.eq('user', user.id)
|
||||
.order('created_at', ascending: false)
|
||||
.limit(1)
|
||||
.single()
|
||||
.execute();
|
||||
|
||||
if (response.data == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
final memory = response.data;
|
||||
final location = memory['location'];
|
||||
final memoryType =
|
||||
location.split('.').last == 'jpg' ? MemoryType.photo : MemoryType.video;
|
||||
final file = await supabase.storage.from('memories').download(location);
|
||||
|
||||
if (file.error != null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return [file.data!, memoryType];
|
||||
}
|
||||
}
|
15
lib/managers/global_values_manager.dart
Normal file
15
lib/managers/global_values_manager.dart
Normal file
@ -0,0 +1,15 @@
|
||||
import 'package:camera/camera.dart';
|
||||
|
||||
class GlobalValuesManager {
|
||||
static List<CameraDescription> _cameras = [];
|
||||
|
||||
static List<CameraDescription> get cameras => [..._cameras];
|
||||
|
||||
static void setCameras(List<CameraDescription> cameras) {
|
||||
if (_cameras.isNotEmpty) {
|
||||
throw Exception('Cameras already set');
|
||||
}
|
||||
|
||||
_cameras = cameras;
|
||||
}
|
||||
}
|
126
lib/screens/login_screen.dart
Normal file
126
lib/screens/login_screen.dart
Normal file
@ -0,0 +1,126 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:share_location/constants/spacing.dart';
|
||||
import 'package:share_location/extensions/snackbar.dart';
|
||||
import 'package:share_location/managers/authentication_manager.dart';
|
||||
import 'package:share_location/screens/main_screen.dart';
|
||||
import 'package:share_location/utils/loadable.dart';
|
||||
import 'package:supabase_flutter/supabase_flutter.dart';
|
||||
|
||||
final supabase = Supabase.instance.client;
|
||||
|
||||
class LoginScreen extends StatefulWidget {
|
||||
static const ID = 'login';
|
||||
|
||||
const LoginScreen({Key? key}) : super(key: key);
|
||||
|
||||
@override
|
||||
State<LoginScreen> createState() => _LoginScreenState();
|
||||
}
|
||||
|
||||
class _LoginScreenState extends AuthState<LoginScreen> with Loadable {
|
||||
final emailController = TextEditingController();
|
||||
final passwordController = TextEditingController();
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
emailController.dispose();
|
||||
passwordController.dispose();
|
||||
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
Future<bool> doesEmailExist() async {
|
||||
// TODO: SECURE PROFILE READ ACCESS TO ONLY ALLOW ACCESS TO EMAIL ADDRESSES
|
||||
final response = await supabase
|
||||
.from('profiles')
|
||||
.select()
|
||||
.match({'username': emailController.text.trim()}).execute();
|
||||
|
||||
return response.data.isNotEmpty;
|
||||
}
|
||||
|
||||
Future<void> signIn() async {
|
||||
if (await doesEmailExist()) {
|
||||
// Login User
|
||||
final response = await supabase.auth.signIn(
|
||||
email: emailController.text.trim(),
|
||||
password: passwordController.text,
|
||||
);
|
||||
final error = response.error;
|
||||
|
||||
if (mounted) {
|
||||
if (error != null) {
|
||||
context.showErrorSnackBar(message: error.message);
|
||||
} else {
|
||||
emailController.clear();
|
||||
passwordController.clear();
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Sign up User
|
||||
final response = await supabase.auth.signUp(
|
||||
emailController.text.trim(),
|
||||
passwordController.text,
|
||||
);
|
||||
|
||||
final error = response.error;
|
||||
|
||||
if (mounted) {
|
||||
if (error != null) {
|
||||
context.showErrorSnackBar(message: error.message);
|
||||
} else {
|
||||
Navigator.pushReplacementNamed(context, MainScreen.ID);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final theme = Theme.of(context);
|
||||
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
title: const Text('Login'),
|
||||
),
|
||||
body: Padding(
|
||||
padding: const EdgeInsets.all(MEDIUM_SPACE),
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
children: <Widget>[
|
||||
Text(
|
||||
'Login',
|
||||
style: theme.textTheme.headline1,
|
||||
),
|
||||
const SizedBox(height: LARGE_SPACE),
|
||||
const Text(
|
||||
'Sign in to your account. If you do not have one already, we will automatically set up one for you.',
|
||||
),
|
||||
const SizedBox(height: MEDIUM_SPACE),
|
||||
TextFormField(
|
||||
controller: emailController,
|
||||
autofocus: true,
|
||||
autofillHints: const [AutofillHints.email],
|
||||
decoration: const InputDecoration(labelText: 'Email'),
|
||||
),
|
||||
const SizedBox(height: SMALL_SPACE),
|
||||
TextFormField(
|
||||
obscureText: true,
|
||||
controller: passwordController,
|
||||
decoration: const InputDecoration(
|
||||
labelText: 'Password',
|
||||
),
|
||||
),
|
||||
const SizedBox(height: MEDIUM_SPACE),
|
||||
ElevatedButton.icon(
|
||||
icon: const Icon(Icons.arrow_right),
|
||||
label: const Text('Login'),
|
||||
onPressed: isLoading ? null : () => callWithLoading(signIn),
|
||||
)
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
261
lib/screens/main_screen.dart
Normal file
261
lib/screens/main_screen.dart
Normal file
@ -0,0 +1,261 @@
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:camera/camera.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:share_location/constants/spacing.dart';
|
||||
import 'package:share_location/extensions/snackbar.dart';
|
||||
import 'package:share_location/managers/file_manager.dart';
|
||||
import 'package:share_location/managers/global_values_manager.dart';
|
||||
import 'package:share_location/screens/main_screen/permissions_required_page.dart';
|
||||
import 'package:share_location/utils/auth_required.dart';
|
||||
import 'package:share_location/utils/loadable.dart';
|
||||
import 'package:share_location/widgets/camera_button.dart';
|
||||
import 'package:share_location/widgets/change_camera_button.dart';
|
||||
import 'package:share_location/widgets/today_photo_button.dart';
|
||||
import 'package:supabase_flutter/supabase_flutter.dart';
|
||||
|
||||
class MainScreen extends StatefulWidget {
|
||||
static const ID = 'main';
|
||||
|
||||
const MainScreen({Key? key}) : super(key: key);
|
||||
|
||||
@override
|
||||
State<MainScreen> createState() => _MainScreenState();
|
||||
}
|
||||
|
||||
class _MainScreenState extends AuthRequiredState<MainScreen> with Loadable {
|
||||
bool isRecording = false;
|
||||
bool hasGrantedPermissions = false;
|
||||
List? lastPhoto;
|
||||
|
||||
late User _user;
|
||||
|
||||
CameraController? controller;
|
||||
|
||||
@override
|
||||
bool get isLoading =>
|
||||
super.isLoading || controller == null || !controller!.value.isInitialized;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
|
||||
callWithLoading(getLastPhoto);
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
controller?.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
void didChangeAppLifecycleState(AppLifecycleState state) {
|
||||
final CameraController? cameraController = controller;
|
||||
|
||||
// App state changed before we got the chance to initialize.
|
||||
if (cameraController == null || !cameraController.value.isInitialized) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (state == AppLifecycleState.inactive) {
|
||||
cameraController.dispose();
|
||||
} else if (state == AppLifecycleState.resumed) {
|
||||
onNewCameraSelected(cameraController.description);
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
void onAuthenticated(Session session) {
|
||||
final user = session.user;
|
||||
|
||||
if (user != null) {
|
||||
_user = user;
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> getLastPhoto() async {
|
||||
final data = await FileManager.getLastFile(_user);
|
||||
|
||||
setState(() {
|
||||
lastPhoto = data;
|
||||
});
|
||||
}
|
||||
|
||||
void onNewCameraSelected(final CameraDescription cameraDescription) async {
|
||||
final previousCameraController = controller;
|
||||
// Instantiating the camera controller
|
||||
final CameraController cameraController = CameraController(
|
||||
cameraDescription,
|
||||
ResolutionPreset.high,
|
||||
imageFormatGroup: ImageFormatGroup.jpeg,
|
||||
);
|
||||
cameraController.setFlashMode(FlashMode.off);
|
||||
|
||||
await previousCameraController?.dispose();
|
||||
|
||||
controller = cameraController;
|
||||
|
||||
// Update UI if controller updates
|
||||
controller!.addListener(() {
|
||||
if (mounted) setState(() {});
|
||||
});
|
||||
|
||||
controller!.initialize().then((_) {
|
||||
if (!mounted) {
|
||||
return;
|
||||
}
|
||||
setState(() {});
|
||||
});
|
||||
}
|
||||
|
||||
Future<void> takePhoto() async {
|
||||
if (controller!.value.isTakingPicture) {
|
||||
return;
|
||||
}
|
||||
|
||||
controller!.setFlashMode(FlashMode.off);
|
||||
final file = File((await controller!.takePicture()).path);
|
||||
|
||||
try {
|
||||
await FileManager.uploadFile(_user, file);
|
||||
} catch (error) {
|
||||
if (mounted) {
|
||||
context.showErrorSnackBar(message: error.toString());
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (mounted) {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
const SnackBar(
|
||||
content: Text('Photo saved.'),
|
||||
backgroundColor: Colors.green,
|
||||
),
|
||||
);
|
||||
|
||||
await getLastPhoto();
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> takeVideo() async {
|
||||
if (!controller!.value.isRecordingVideo) {
|
||||
// Recording has already been stopped
|
||||
return;
|
||||
}
|
||||
|
||||
setState(() {
|
||||
isRecording = false;
|
||||
});
|
||||
|
||||
final file = File((await controller!.stopVideoRecording()).path);
|
||||
|
||||
try {
|
||||
await FileManager.uploadFile(_user, file);
|
||||
} catch (error) {
|
||||
if (mounted) {
|
||||
context.showErrorSnackBar(message: error.toString());
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (mounted) {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
const SnackBar(
|
||||
content: Text('Video saved.'),
|
||||
backgroundColor: Colors.green,
|
||||
),
|
||||
);
|
||||
await getLastPhoto();
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
backgroundColor: Colors.black,
|
||||
body: SafeArea(
|
||||
child: () {
|
||||
if (!hasGrantedPermissions) {
|
||||
return Center(
|
||||
child: PermissionsRequiredPage(
|
||||
onPermissionsGranted: () {
|
||||
onNewCameraSelected(GlobalValuesManager.cameras[0]);
|
||||
|
||||
setState(() {
|
||||
hasGrantedPermissions = true;
|
||||
});
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
if (isLoading) {
|
||||
return const Center(
|
||||
child: CircularProgressIndicator(),
|
||||
);
|
||||
}
|
||||
|
||||
return SizedBox(
|
||||
width: MediaQuery.of(context).size.width,
|
||||
height: MediaQuery.of(context).size.height,
|
||||
child: Stack(
|
||||
alignment: Alignment.center,
|
||||
children: <Widget>[
|
||||
controller!.buildPreview(),
|
||||
Column(
|
||||
mainAxisAlignment: MainAxisAlignment.end,
|
||||
children: <Widget>[
|
||||
Padding(
|
||||
padding: const EdgeInsets.all(LARGE_SPACE),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: <Widget>[
|
||||
ChangeCameraButton(onChangeCamera: () {
|
||||
final currentCameraIndex = GlobalValuesManager
|
||||
.cameras
|
||||
.indexOf(controller!.description);
|
||||
final availableCameras =
|
||||
GlobalValuesManager.cameras.length;
|
||||
|
||||
onNewCameraSelected(
|
||||
GlobalValuesManager.cameras[
|
||||
(currentCameraIndex + 1) % availableCameras],
|
||||
);
|
||||
}),
|
||||
CameraButton(
|
||||
active: isRecording,
|
||||
onVideoBegin: () async {
|
||||
if (controller!.value.isRecordingVideo) {
|
||||
// A recording has already started, do nothing.
|
||||
return;
|
||||
}
|
||||
|
||||
setState(() {
|
||||
isRecording = true;
|
||||
});
|
||||
|
||||
await controller!.startVideoRecording();
|
||||
},
|
||||
onVideoEnd: takeVideo,
|
||||
onPhotoShot: takePhoto,
|
||||
),
|
||||
lastPhoto == null
|
||||
? TodayPhotoButton()
|
||||
: TodayPhotoButton(
|
||||
data: lastPhoto![0],
|
||||
type: lastPhoto![1],
|
||||
),
|
||||
],
|
||||
),
|
||||
)
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}(),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
108
lib/screens/main_screen/permissions_required_page.dart
Normal file
108
lib/screens/main_screen/permissions_required_page.dart
Normal file
@ -0,0 +1,108 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:permission_handler/permission_handler.dart';
|
||||
import 'package:share_location/constants/spacing.dart';
|
||||
|
||||
class PermissionsRequiredPage extends StatefulWidget {
|
||||
final VoidCallback onPermissionsGranted;
|
||||
|
||||
const PermissionsRequiredPage({
|
||||
Key? key,
|
||||
required this.onPermissionsGranted,
|
||||
}) : super(key: key);
|
||||
|
||||
@override
|
||||
State<PermissionsRequiredPage> createState() =>
|
||||
_PermissionsRequiredPageState();
|
||||
}
|
||||
|
||||
class _PermissionsRequiredPageState extends State<PermissionsRequiredPage> {
|
||||
bool hasDeniedForever = false;
|
||||
bool hasGrantedCameraPermission = false;
|
||||
bool hasGrantedMicrophonePermission = false;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
|
||||
checkPermissions();
|
||||
}
|
||||
|
||||
Future<void> checkPermissions() async {
|
||||
final cameraStatus = await Permission.camera.status;
|
||||
final microphoneStatus = await Permission.microphone.status;
|
||||
|
||||
setState(() {
|
||||
hasGrantedCameraPermission = cameraStatus.isGranted;
|
||||
hasGrantedMicrophonePermission = microphoneStatus.isGranted;
|
||||
});
|
||||
|
||||
if (cameraStatus.isPermanentlyDenied ||
|
||||
microphoneStatus.isPermanentlyDenied) {
|
||||
setState(() {
|
||||
hasDeniedForever = true;
|
||||
});
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (cameraStatus.isGranted && microphoneStatus.isGranted) {
|
||||
widget.onPermissionsGranted();
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
children: <Widget>[
|
||||
Text(
|
||||
'Permissions Required',
|
||||
style: Theme.of(context).textTheme.headline1,
|
||||
),
|
||||
const SizedBox(height: MEDIUM_SPACE),
|
||||
const Text(
|
||||
'Please grant permissions to use this app',
|
||||
),
|
||||
const SizedBox(height: LARGE_SPACE),
|
||||
if (hasDeniedForever) ...[
|
||||
const Text(
|
||||
'You have permanently denied permissions required to use this app. Please enable them in the settings.',
|
||||
),
|
||||
const SizedBox(height: LARGE_SPACE),
|
||||
TextButton.icon(
|
||||
onPressed: () => openAppSettings(),
|
||||
icon: const Icon(Icons.settings),
|
||||
label: const Text('Open Settings'),
|
||||
),
|
||||
] else ...[
|
||||
TextButton.icon(
|
||||
onPressed: hasGrantedCameraPermission
|
||||
? null
|
||||
: () async {
|
||||
await Permission.camera.request();
|
||||
await checkPermissions();
|
||||
},
|
||||
icon: const Icon(Icons.camera_alt),
|
||||
label: Text(
|
||||
'Grant camera permission${hasGrantedCameraPermission ? ' - Granted!' : ''}',
|
||||
),
|
||||
),
|
||||
const SizedBox(height: MEDIUM_SPACE),
|
||||
TextButton.icon(
|
||||
onPressed: hasGrantedMicrophonePermission
|
||||
? null
|
||||
: () async {
|
||||
await Permission.microphone.request();
|
||||
await checkPermissions();
|
||||
},
|
||||
icon: const Icon(Icons.mic),
|
||||
label: Text(
|
||||
'Grant microphone permission ${hasGrantedMicrophonePermission ? ' - Granted!' : ''}',
|
||||
),
|
||||
),
|
||||
],
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
@ -1,5 +1,7 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:share_location/constants/spacing.dart';
|
||||
import 'package:share_location/managers/startup_page_manager.dart';
|
||||
import 'package:share_location/screens/main_screen.dart';
|
||||
import 'package:share_location/widgets/logo.dart';
|
||||
|
||||
class WelcomeScreen extends StatelessWidget {
|
||||
@ -38,9 +40,11 @@ class WelcomeScreen extends StatelessWidget {
|
||||
),
|
||||
const SizedBox(height: LARGE_SPACE),
|
||||
ElevatedButton.icon(
|
||||
icon: Icon(Icons.arrow_right),
|
||||
label: Text('Start'),
|
||||
onPressed: () {},
|
||||
icon: const Icon(Icons.arrow_right),
|
||||
label: const Text('Start'),
|
||||
onPressed: () {
|
||||
StartupPageManager.navigateToNewPage(context, MainScreen.ID);
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
|
14
lib/utils/auth_required.dart
Normal file
14
lib/utils/auth_required.dart
Normal file
@ -0,0 +1,14 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:share_location/screens/login_screen.dart';
|
||||
import 'package:supabase_flutter/supabase_flutter.dart';
|
||||
|
||||
class AuthRequiredState<T extends StatefulWidget>
|
||||
extends SupabaseAuthRequiredState<T> {
|
||||
@override
|
||||
void onUnauthenticated() {
|
||||
if (mounted) {
|
||||
Navigator.of(context)
|
||||
.pushNamedAndRemoveUntil(LoginScreen.ID, (route) => false);
|
||||
}
|
||||
}
|
||||
}
|
21
lib/utils/loadable.dart
Normal file
21
lib/utils/loadable.dart
Normal file
@ -0,0 +1,21 @@
|
||||
mixin Loadable {
|
||||
bool _isLoading = false;
|
||||
|
||||
bool get isLoading => _isLoading;
|
||||
|
||||
void setState(void Function() callback);
|
||||
|
||||
Future<void> callWithLoading(Future<void> Function() callback) async {
|
||||
setState(() {
|
||||
_isLoading = true;
|
||||
});
|
||||
|
||||
try {
|
||||
await callback();
|
||||
} finally {
|
||||
setState(() {
|
||||
_isLoading = false;
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
74
lib/widgets/camera_button.dart
Normal file
74
lib/widgets/camera_button.dart
Normal file
@ -0,0 +1,74 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
|
||||
class CameraButton extends StatelessWidget {
|
||||
final bool active;
|
||||
final VoidCallback onPhotoShot;
|
||||
final VoidCallback onVideoBegin;
|
||||
final VoidCallback onVideoEnd;
|
||||
|
||||
const CameraButton({
|
||||
Key? key,
|
||||
required this.onPhotoShot,
|
||||
required this.onVideoBegin,
|
||||
required this.onVideoEnd,
|
||||
this.active = false,
|
||||
}) : super(key: key);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return InkWell(
|
||||
onTap: () {
|
||||
HapticFeedback.heavyImpact();
|
||||
|
||||
if (active) {
|
||||
onVideoEnd();
|
||||
} else {
|
||||
onPhotoShot();
|
||||
}
|
||||
},
|
||||
onLongPress: () {
|
||||
HapticFeedback.heavyImpact();
|
||||
|
||||
if (active) {
|
||||
onVideoEnd();
|
||||
} else {
|
||||
onVideoBegin();
|
||||
}
|
||||
},
|
||||
child: Stack(
|
||||
alignment: Alignment.center,
|
||||
children: active
|
||||
? const <Widget>[
|
||||
Icon(
|
||||
Icons.circle,
|
||||
size: 75,
|
||||
color: Colors.white,
|
||||
),
|
||||
Icon(
|
||||
Icons.circle,
|
||||
size: 65,
|
||||
color: Colors.red,
|
||||
),
|
||||
Icon(
|
||||
Icons.stop,
|
||||
size: 45,
|
||||
color: Colors.white,
|
||||
),
|
||||
]
|
||||
: <Widget>[
|
||||
Icon(
|
||||
Icons.circle,
|
||||
size: 75,
|
||||
color: Colors.white.withOpacity(.2),
|
||||
),
|
||||
const Icon(
|
||||
Icons.circle,
|
||||
size: 50,
|
||||
color: Colors.white,
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
38
lib/widgets/change_camera_button.dart
Normal file
38
lib/widgets/change_camera_button.dart
Normal file
@ -0,0 +1,38 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
|
||||
class ChangeCameraButton extends StatelessWidget {
|
||||
final VoidCallback onChangeCamera;
|
||||
|
||||
const ChangeCameraButton({
|
||||
Key? key,
|
||||
required this.onChangeCamera,
|
||||
}) : super(key: key);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return InkWell(
|
||||
enableFeedback: false,
|
||||
highlightColor: Colors.transparent,
|
||||
onTap: () {
|
||||
HapticFeedback.heavyImpact();
|
||||
onChangeCamera();
|
||||
},
|
||||
child: Stack(
|
||||
alignment: Alignment.center,
|
||||
children: <Widget>[
|
||||
Icon(
|
||||
Icons.circle,
|
||||
size: 60,
|
||||
color: Colors.white.withOpacity(.2),
|
||||
),
|
||||
Icon(
|
||||
Icons.camera_alt,
|
||||
size: 30,
|
||||
color: Colors.white,
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
99
lib/widgets/today_photo_button.dart
Normal file
99
lib/widgets/today_photo_button.dart
Normal file
@ -0,0 +1,99 @@
|
||||
import 'dart:io';
|
||||
import 'dart:typed_data';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:path_provider/path_provider.dart';
|
||||
import 'package:share_location/constants/spacing.dart';
|
||||
import 'package:share_location/enums.dart';
|
||||
import 'package:video_player/video_player.dart';
|
||||
|
||||
class TodayPhotoButton extends StatefulWidget {
|
||||
final Uint8List? data;
|
||||
final MemoryType? type;
|
||||
|
||||
const TodayPhotoButton({
|
||||
Key? key,
|
||||
this.data,
|
||||
this.type,
|
||||
}) : super(key: key);
|
||||
|
||||
@override
|
||||
State<TodayPhotoButton> createState() => _TodayPhotoButtonState();
|
||||
}
|
||||
|
||||
class _TodayPhotoButtonState extends State<TodayPhotoButton> {
|
||||
VideoPlayerController? videoController;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
|
||||
if (widget.type == MemoryType.video) {
|
||||
initializeVideo();
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> initializeVideo() async {
|
||||
final tempDir = await getTemporaryDirectory();
|
||||
final file = await File('${tempDir.path}/video.mp4').create();
|
||||
file.writeAsBytesSync(widget.data!);
|
||||
|
||||
videoController = VideoPlayerController.file(file);
|
||||
videoController!.initialize().then((value) {
|
||||
setState(() {});
|
||||
videoController!.setLooping(true);
|
||||
videoController!.play();
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
videoController?.dispose();
|
||||
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return InkWell(
|
||||
child: Container(
|
||||
width: 45,
|
||||
height: 45,
|
||||
decoration: BoxDecoration(
|
||||
border: Border.all(
|
||||
color: Colors.white,
|
||||
width: 2,
|
||||
),
|
||||
borderRadius: BorderRadius.circular(SMALL_SPACE),
|
||||
color: Colors.grey,
|
||||
),
|
||||
child: ClipRRect(
|
||||
borderRadius: BorderRadius.circular(SMALL_SPACE),
|
||||
child: () {
|
||||
if (widget.data == null) {
|
||||
return SizedBox();
|
||||
}
|
||||
|
||||
switch (widget.type) {
|
||||
case MemoryType.photo:
|
||||
return Image.memory(
|
||||
widget.data as Uint8List,
|
||||
fit: BoxFit.cover,
|
||||
);
|
||||
case MemoryType.video:
|
||||
if (videoController == null) {
|
||||
return const SizedBox();
|
||||
}
|
||||
|
||||
return AspectRatio(
|
||||
aspectRatio: videoController!.value.aspectRatio,
|
||||
child: VideoPlayer(videoController!),
|
||||
);
|
||||
default:
|
||||
return const SizedBox();
|
||||
}
|
||||
}()),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
443
pubspec.lock
443
pubspec.lock
@ -15,6 +15,41 @@ packages:
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "2.1.0"
|
||||
camera:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: camera
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "0.10.0+1"
|
||||
camera_android:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: camera_android
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "0.10.0+1"
|
||||
camera_avfoundation:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: camera_avfoundation
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "0.9.8+3"
|
||||
camera_platform_interface:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: camera_platform_interface
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "2.2.0"
|
||||
camera_web:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: camera_web
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "0.3.0"
|
||||
characters:
|
||||
dependency: transitive
|
||||
description:
|
||||
@ -43,6 +78,27 @@ packages:
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "1.16.0"
|
||||
cross_file:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: cross_file
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "0.3.3+1"
|
||||
crypto:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: crypto
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "3.0.2"
|
||||
csslib:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: csslib
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "0.17.2"
|
||||
cupertino_icons:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
@ -57,6 +113,20 @@ packages:
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "1.3.0"
|
||||
ffi:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: ffi
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "2.0.1"
|
||||
file:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: file
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "6.1.2"
|
||||
flutter:
|
||||
dependency: "direct main"
|
||||
description: flutter
|
||||
@ -69,6 +139,13 @@ packages:
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "2.0.1"
|
||||
flutter_plugin_android_lifecycle:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: flutter_plugin_android_lifecycle
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "2.0.7"
|
||||
flutter_secure_storage:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
@ -128,6 +205,55 @@ packages:
|
||||
description: flutter
|
||||
source: sdk
|
||||
version: "0.0.0"
|
||||
functions_client:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: functions_client
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "0.0.1-dev.5"
|
||||
gotrue:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: gotrue
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "0.2.3"
|
||||
hive:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: hive
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "2.2.3"
|
||||
hive_flutter:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: hive_flutter
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "1.1.0"
|
||||
html:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: html
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "0.15.0"
|
||||
http:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: http
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "0.13.5"
|
||||
http_parser:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: http_parser
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "4.0.1"
|
||||
js:
|
||||
dependency: transitive
|
||||
description:
|
||||
@ -135,6 +261,13 @@ packages:
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "0.6.4"
|
||||
jwt_decode:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: jwt_decode
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "0.3.1"
|
||||
lints:
|
||||
dependency: transitive
|
||||
description:
|
||||
@ -163,6 +296,13 @@ packages:
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "1.7.0"
|
||||
mime:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: mime
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "1.0.2"
|
||||
path:
|
||||
dependency: transitive
|
||||
description:
|
||||
@ -184,6 +324,90 @@ packages:
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "1.0.1"
|
||||
path_provider:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: path_provider
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "2.0.11"
|
||||
path_provider_android:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: path_provider_android
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "2.0.19"
|
||||
path_provider_ios:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: path_provider_ios
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "2.0.11"
|
||||
path_provider_linux:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: path_provider_linux
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "2.1.7"
|
||||
path_provider_macos:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: path_provider_macos
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "2.0.6"
|
||||
path_provider_platform_interface:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: path_provider_platform_interface
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "2.0.4"
|
||||
path_provider_windows:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: path_provider_windows
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "2.1.2"
|
||||
permission_handler:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: permission_handler
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "10.0.0"
|
||||
permission_handler_android:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: permission_handler_android
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "10.0.0"
|
||||
permission_handler_apple:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: permission_handler_apple
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "9.0.4"
|
||||
permission_handler_platform_interface:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: permission_handler_platform_interface
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "3.7.0"
|
||||
permission_handler_windows:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: permission_handler_windows
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "0.1.0"
|
||||
petitparser:
|
||||
dependency: transitive
|
||||
description:
|
||||
@ -191,6 +415,13 @@ packages:
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "5.0.0"
|
||||
platform:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: platform
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "3.1.0"
|
||||
plugin_platform_interface:
|
||||
dependency: transitive
|
||||
description:
|
||||
@ -198,6 +429,34 @@ packages:
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "2.1.2"
|
||||
postgrest:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: postgrest
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "0.1.11"
|
||||
process:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: process
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "4.2.4"
|
||||
quiver:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: quiver
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "3.1.0"
|
||||
realtime_client:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: realtime_client
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "0.1.15"
|
||||
sky_engine:
|
||||
dependency: transitive
|
||||
description: flutter
|
||||
@ -217,6 +476,13 @@ packages:
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "1.10.0"
|
||||
storage_client:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: storage_client
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "0.0.6+2"
|
||||
stream_channel:
|
||||
dependency: transitive
|
||||
description:
|
||||
@ -224,6 +490,13 @@ packages:
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "2.1.0"
|
||||
stream_transform:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: stream_transform
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "2.0.0"
|
||||
string_scanner:
|
||||
dependency: transitive
|
||||
description:
|
||||
@ -231,6 +504,20 @@ packages:
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "1.1.0"
|
||||
supabase:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: supabase
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "0.3.6"
|
||||
supabase_flutter:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: supabase_flutter
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "0.3.3"
|
||||
term_glyph:
|
||||
dependency: transitive
|
||||
description:
|
||||
@ -245,6 +532,104 @@ packages:
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "0.4.9"
|
||||
typed_data:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: typed_data
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "1.3.1"
|
||||
uni_links:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: uni_links
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "0.5.1"
|
||||
uni_links_platform_interface:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: uni_links_platform_interface
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "1.0.0"
|
||||
uni_links_web:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: uni_links_web
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "0.1.0"
|
||||
universal_io:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: universal_io
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "2.0.4"
|
||||
url_launcher:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: url_launcher
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "6.1.5"
|
||||
url_launcher_android:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: url_launcher_android
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "6.0.17"
|
||||
url_launcher_ios:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: url_launcher_ios
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "6.0.17"
|
||||
url_launcher_linux:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: url_launcher_linux
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "3.0.1"
|
||||
url_launcher_macos:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: url_launcher_macos
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "3.0.1"
|
||||
url_launcher_platform_interface:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: url_launcher_platform_interface
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "2.1.0"
|
||||
url_launcher_web:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: url_launcher_web
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "2.0.13"
|
||||
url_launcher_windows:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: url_launcher_windows
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "3.0.1"
|
||||
uuid:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: uuid
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "3.0.6"
|
||||
vector_math:
|
||||
dependency: transitive
|
||||
description:
|
||||
@ -252,6 +637,62 @@ packages:
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "2.1.2"
|
||||
video_player:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: video_player
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "2.4.6"
|
||||
video_player_android:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: video_player_android
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "2.3.8"
|
||||
video_player_avfoundation:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: video_player_avfoundation
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "2.3.5"
|
||||
video_player_platform_interface:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: video_player_platform_interface
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "5.1.4"
|
||||
video_player_web:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: video_player_web
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "2.0.12"
|
||||
web_socket_channel:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: web_socket_channel
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "2.2.0"
|
||||
win32:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: win32
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "2.7.0"
|
||||
xdg_directories:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: xdg_directories
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "0.2.0+1"
|
||||
xml:
|
||||
dependency: transitive
|
||||
description:
|
||||
@ -261,4 +702,4 @@ packages:
|
||||
version: "6.1.0"
|
||||
sdks:
|
||||
dart: ">=2.17.5 <3.0.0"
|
||||
flutter: ">=2.11.0-0.1.pre"
|
||||
flutter: ">=3.0.0"
|
||||
|
@ -36,6 +36,12 @@ dependencies:
|
||||
cupertino_icons: ^1.0.2
|
||||
flutter_svg: ^1.1.3
|
||||
flutter_secure_storage: ^5.1.0
|
||||
camera: ^0.10.0+1
|
||||
permission_handler: ^10.0.0
|
||||
supabase_flutter: ^0.3.3
|
||||
uuid: ^3.0.6
|
||||
video_player: ^2.4.6
|
||||
path_provider: ^2.0.11
|
||||
|
||||
dev_dependencies:
|
||||
flutter_test:
|
||||
|
Loading…
x
Reference in New Issue
Block a user