Some visual adjustments.

master
Rafael 2024-06-14 14:28:59 +02:00
parent 94e78e91da
commit 9a7a8f9f39
15 changed files with 270 additions and 241 deletions

View File

@ -0,0 +1,42 @@
import 'package:flutter/material.dart';
import 'package:flutter_svg/svg.dart';
import '../models/language.dart';
import '../utils/list_utils.dart';
class MyLanguageList extends StatelessWidget {
final List<Language> langList;
final double? iconHeight;
final double? textSize;
const MyLanguageList(
{super.key, required this.langList, this.iconHeight = 16, this.textSize});
@override
Widget build(BuildContext context) {
List<Language> sortedLanguages = sortLanguageList(langList);
return Wrap(
children: sortedLanguages.map(
(language) {
return Row(
mainAxisSize: MainAxisSize.min,
children: [
SvgPicture.asset(
language.iconFile,
height: iconHeight,
),
const SizedBox(width: 4.0),
Text(
language.name,
style: TextStyle(fontSize: textSize),
),
// Space between each language icon pair
const SizedBox(width: 8.0),
],
);
},
).toList(),
);
}
}

View File

@ -5,6 +5,7 @@ import '../constants.dart';
import '../enumerations.dart';
import '../forms/risks_form.dart';
import '../services/auth/auth_service.dart';
import '../utils/helper_dialogs.dart';
class CultureValuesFormPage extends StatefulWidget {
const CultureValuesFormPage(
@ -85,15 +86,13 @@ class CultureValuesFormPageState extends State<CultureValuesFormPage> {
);
return true;
} catch (e) {
_showSnackBar(e.toString());
_showSnackBar('Error saving data: ${e.toString()}');
return false;
}
}
void _showSnackBar(String message) {
ScaffoldMessenger.of(context).showSnackBar(SnackBar(
content: Text(message),
));
showErrorSnackBar(context, message);
}
bool isWorkValueSelected() {
@ -105,18 +104,12 @@ class CultureValuesFormPageState extends State<CultureValuesFormPage> {
Future<void> handleSubmit() async {
if (!isWorkValueSelected()) {
ScaffoldMessenger.of(context).showSnackBar(SnackBar(
content: Text(
'Please select at least ${minWorkValueOption.toString()} and '
'at most ${maxWorkValueOption.toString()} work life values.',
),
));
_showSnackBar(
'Please select at least ${minWorkValueOption.toString()} and at most ${maxWorkValueOption.toString()} work life values.');
return;
}
if (selectedCultureOption == null) {
ScaffoldMessenger.of(context).showSnackBar(const SnackBar(
content: Text('Please select a corporate culture.'),
));
_showSnackBar('Please select a corporate culture.');
return;
}
// Handle the form submission logic here
@ -167,8 +160,28 @@ class CultureValuesFormPageState extends State<CultureValuesFormPage> {
'Work life and corporate culture',
style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold),
),
const Text(
'What values are most important to you in your working environment?',
const Align(
alignment: Alignment.centerLeft,
child: Text('Which corporate culture suits you best?'),
),
...CultureOption.values.map((option) {
return RadioListTile(
title: Text(option.displayName),
value: option,
groupValue: selectedCultureOption,
onChanged: (CultureOption? value) {
setState(() {
selectedCultureOption = value;
});
},
);
}),
const SizedBox(height: 40),
const Align(
alignment: Alignment.centerLeft,
child: Text(
'What values are most important to you in your working environment?',
),
),
...WorkValueOption.values.map((option) {
return CheckboxListTile(
@ -184,36 +197,14 @@ class CultureValuesFormPageState extends State<CultureValuesFormPage> {
selectedValueOptions[option] = value!;
} else if (value == true &&
selectedCount >= maxWorkValueOption) {
ScaffoldMessenger.of(context)
.showSnackBar(const SnackBar(
content: Text(
'You can only select a maximum of two values.',
),
));
_showSnackBar(
'You can only select a maximum of two values.',
);
}
});
},
);
}),
const SizedBox(height: 40),
const Align(
alignment: Alignment.centerLeft,
child: Text('Which corporate culture suits you best?')),
DropdownButton<CultureOption>(
hint: const Text('Choose a corporate culture'),
value: selectedCultureOption,
onChanged: (CultureOption? newValue) {
setState(() {
selectedCultureOption = newValue;
});
},
items: CultureOption.values.map((CultureOption option) {
return DropdownMenuItem<CultureOption>(
value: option,
child: Text(option.displayName),
);
}).toList(),
),
const SizedBox(height: 20),
ElevatedButton(
onPressed: handleSubmit,

View File

@ -6,7 +6,7 @@ import '../constants.dart';
import '../enumerations.dart';
import '../models/swipe.dart';
import '../services/auth/auth_service.dart';
import '../utils/helper.dart';
import '../utils/helper_dialogs.dart';
import 'user_profile_page.dart';
class LikedUsersPage extends StatefulWidget {
@ -125,8 +125,9 @@ class LikedUsersPageState extends State<LikedUsersPage> {
context: context,
builder: (BuildContext context) {
return AlertDialog(
title: const Text('Confirm Unlike'),
content: Text('Are you sure you want to unlike $userName?'),
title: const Text('Confirm Removal'),
content:
Text('Are you sure you want to remove $userName from your list?'),
actions: [
TextButton(
onPressed: () => Navigator.of(context).pop(false),

View File

@ -2,7 +2,7 @@ import 'package:firebase_auth/firebase_auth.dart';
import 'package:flutter/material.dart';
import '../components/my_button.dart';
import '../components/my_textfield.dart';
import '../utils/helper.dart';
import '../utils/helper_dialogs.dart';
import '../services/auth/auth_service.dart';
class LoginPage extends StatelessWidget {

View File

@ -2,7 +2,7 @@ import 'package:flutter/material.dart';
import 'package:firebase_auth/firebase_auth.dart';
import '../components/my_button.dart';
import '../components/my_textfield.dart';
import '../utils/helper.dart';
import '../utils/helper_dialogs.dart';
import '../services/auth/auth_service.dart';
import '../services/user_service.dart';

View File

@ -13,7 +13,7 @@ import '../models/language.dart';
import '../models/language_setting.dart';
import '../models/location.dart';
import '../services/auth/auth_service.dart';
import '../utils/helper.dart';
import '../utils/helper_dialogs.dart';
import '../utils/math.dart';
class UserDataPage extends StatefulWidget {

View File

@ -1,11 +1,11 @@
import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:collection/collection.dart';
import 'package:flutter/material.dart';
import 'package:flutter_svg/svg.dart';
import 'package:percent_indicator/circular_percent_indicator.dart';
import 'package:swipable_stack/swipable_stack.dart';
import '../components/card_overlay.dart';
import '../components/language_list.dart';
import '../constants.dart';
import '../forms/matched_screen.dart';
import '../models/language.dart';
@ -409,16 +409,6 @@ class UserMatchingPageState extends State<UserMatchingPage> {
Widget _buildUserCard(UserProfile userProfile) {
String? profileImageUrl = userProfile.profilePictureUrl;
// Sort the languages according to the given order below
List<Language> sortedLanguages = List.from(userProfile.languages);
sortedLanguages.sort((a, b) {
if (a.code == 'de') return -1; // German first
if (b.code == 'de') return 1; // German first
if (a.code == 'en') return -1; // English second
if (b.code == 'en') return 1; // English second
return a.name.compareTo(b.name); // All others by name ascending
});
String pronoun = getPronoun(userProfile.gender);
double shortDist =
@ -499,27 +489,10 @@ class UserMatchingPageState extends State<UserMatchingPage> {
],
),
const SizedBox(height: 4),
Wrap(
children: sortedLanguages.map(
(language) {
return Row(
mainAxisSize: MainAxisSize.min,
children: [
SvgPicture.asset(
language.iconFile,
height: 12.0,
),
const SizedBox(width: 4.0),
Text(language.name),
// Space between each language icon pair
const SizedBox(width: 8.0),
],
);
},
).toList(),
MyLanguageList(
langList: userProfile.languages,
iconHeight: 12,
),
//Text('Risk type: ${userProfile.risk.displayName}'),
],
),
),

View File

@ -1,6 +1,7 @@
import 'package:flutter/material.dart';
import 'package:firebase_auth/firebase_auth.dart';
import '../components/language_list.dart';
import '../constants.dart';
import '../enumerations.dart';
import '../forms/corporate_culture_form.dart';
@ -350,7 +351,7 @@ class _UserProfilePageState extends State<UserProfilePage> {
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'Vision and Goals',
'Corporate values',
style: TextStyle(
color: Theme.of(context).colorScheme.primary,
),
@ -502,29 +503,53 @@ class _UserProfilePageState extends State<UserProfilePage> {
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
if (isOwner)
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'Age',
style: TextStyle(
color: Theme.of(context).colorScheme.primary,
),
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'Locations',
style: TextStyle(
color: Theme.of(context).colorScheme.primary,
),
),
if (myData.locations.isEmpty)
const Text('n/a', style: TextStyle(fontSize: 16)),
if (myData.locations
.containsKey(Constants.dbDocMainLocation))
Text(
(age > 0
? '$age years old, born ${myData.born}'
: 'n/a'),
style: const TextStyle(fontSize: 16)),
],
),
myData.locations[Constants.dbDocMainLocation]
.toString(),
style: const TextStyle(fontSize: 16),
),
if (myData.locations
.containsKey(Constants.dbDocSecondLocation))
Text(
myData.locations[Constants.dbDocSecondLocation]
.toString(),
style: const TextStyle(fontSize: 16),
),
],
),
if (isOwner)
_editButton(context, editUserDataInfo,
alignment: Alignment.bottomRight),
],
],
),
if (isOwner) const SizedBox(height: 16),
if (isOwner)
Text(
'Age',
style: TextStyle(
color: Theme.of(context).colorScheme.primary,
),
),
if (isOwner)
Text(
(age > 0 ? '$age years old, born ${myData.born}' : 'n/a'),
style: const TextStyle(fontSize: 16),
),
if (isOwner) const SizedBox(height: 16),
if (isOwner)
@ -544,29 +569,10 @@ class _UserProfilePageState extends State<UserProfilePage> {
color: Theme.of(context).colorScheme.primary,
),
),
Text(
myData.languages.map((lang) => lang.name).join(', '),
style: const TextStyle(fontSize: 16),
MyLanguageList(
langList: myData.languages,
textSize: 16,
),
const SizedBox(height: 16),
Text(
'Locations',
style: TextStyle(
color: Theme.of(context).colorScheme.primary,
),
),
if (myData.locations.isEmpty)
const Text('n/a', style: TextStyle(fontSize: 16)),
if (myData.locations.containsKey(Constants.dbDocMainLocation))
Text(
myData.locations[Constants.dbDocMainLocation].toString(),
style: const TextStyle(fontSize: 16),
),
if (myData.locations.containsKey(Constants.dbDocSecondLocation))
Text(
myData.locations[Constants.dbDocSecondLocation].toString(),
style: const TextStyle(fontSize: 16),
),
],
),
),
@ -603,11 +609,13 @@ class _UserProfilePageState extends State<UserProfilePage> {
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text(
isOwner
? myData.name
: '${myData.name} ${ageInfo(myData.born)}'.trim(),
style: const TextStyle(fontSize: 24),
Flexible(
child: Text(
isOwner
? myData.name
: '${myData.name} ${ageInfo(myData.born)}'.trim(),
style: const TextStyle(fontSize: 24),
),
),
genderIcon,
],
@ -637,8 +645,7 @@ class _UserProfilePageState extends State<UserProfilePage> {
{Alignment alignment = Alignment.topRight}) {
return Align(
alignment: alignment,
child: OutlinedButton.icon(
label: const Text('Edit'),
child: IconButton(
icon: const Icon(Icons.edit),
onPressed: onPressedFunction,
),

View File

@ -5,7 +5,7 @@ import '../constants.dart';
import '../enumerations.dart';
import '../forms/corporate_culture_form.dart';
import '../services/auth/auth_service.dart';
import '../utils/helper.dart';
import '../utils/helper_dialogs.dart';
class UserVisionPage extends StatefulWidget {
const UserVisionPage(
@ -155,26 +155,6 @@ class UserVisionPageState extends State<UserVisionPage> {
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const Text(
'Vision and goals',
style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold),
),
const Text('What is your long-term vision for a startup?'),
...VisionOption.values.map((option) {
return CheckboxListTile(
title: Text(option.displayName),
value: selectedVisionOptions[option],
controlAffinity: ListTileControlAffinity.platform,
onChanged: (bool? value) {
if (value != null) {
setState(() {
selectedVisionOptions[option] = value;
});
}
},
);
}),
const SizedBox(height: 40),
const Text(
'Availability and commitment',
style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold),
@ -194,6 +174,26 @@ class UserVisionPageState extends State<UserVisionPage> {
},
);
}),
const SizedBox(height: 40),
const Text(
'Vision and goals',
style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold),
),
const Text('What is your long-term vision for a startup?'),
...VisionOption.values.map((option) {
return CheckboxListTile(
title: Text(option.displayName),
value: selectedVisionOptions[option],
controlAffinity: ListTileControlAffinity.platform,
onChanged: (bool? value) {
if (value != null) {
setState(() {
selectedVisionOptions[option] = value;
});
}
},
);
}),
const SizedBox(height: 20),
Center(
child: ElevatedButton(

View File

@ -1,6 +1,4 @@
import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:collection/collection.dart';
import 'package:flutter/material.dart';
import 'package:flutter/foundation.dart' show kIsWeb;
import 'dart:io' show Platform;
import '../enumerations.dart';
@ -14,11 +12,6 @@ bool get isMobile {
}
}
/// Compare two lists by their content ignoring their elements order.
bool equalContent(List<dynamic> list1, List<dynamic> list2) {
return const DeepCollectionEquality.unordered().equals(list1, list2);
}
/// Creates a composite ID from the passed [ids].
/// In the format id(1)_id(n)
String getCompoundId(List<String> ids) {
@ -94,38 +87,3 @@ String getDisplayText(dynamic option) {
// Fallback to default toString if not an enum
return option.toString().split('.').last;
}
/// Show a simple message dialog
void showMsg(BuildContext context, String title, String content) {
showDialog(
context: context,
builder: (context) => AlertDialog(
title: Text(title),
content: Text(content),
actions: [
TextButton(
onPressed: () {
Navigator.of(context).pop();
},
child: const Text('OK'),
),
],
),
);
}
/// Show a red colored SnackBar with a [Dismiss] Button
void showErrorSnackBar(BuildContext context, String message) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text(message),
backgroundColor: Colors.red,
action: SnackBarAction(
label: 'Dismiss',
onPressed: () {
ScaffoldMessenger.of(context).hideCurrentSnackBar();
},
),
),
);
}

View File

@ -0,0 +1,36 @@
import 'package:flutter/material.dart';
/// Show a simple message dialog
void showMsg(BuildContext context, String title, String content) {
showDialog(
context: context,
builder: (context) => AlertDialog(
title: Text(title),
content: Text(content),
actions: [
TextButton(
onPressed: () {
Navigator.of(context).pop();
},
child: const Text('OK'),
),
],
),
);
}
/// Show a red colored SnackBar with a [Dismiss] Button
void showErrorSnackBar(BuildContext context, String message) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text(message),
backgroundColor: Colors.red,
action: SnackBarAction(
label: 'Dismiss',
onPressed: () {
ScaffoldMessenger.of(context).hideCurrentSnackBar();
},
),
),
);
}

View File

@ -0,0 +1,23 @@
import 'package:collection/collection.dart';
import '../models/language.dart';
/// Compare two lists by their content ignoring their elements order.
bool equalContent(List<dynamic> list1, List<dynamic> list2) {
return const DeepCollectionEquality.unordered().equals(list1, list2);
}
/// Sort the given list of languages so that German appears first, English second, and all other languages follow in alphabetical order.
List<Language> sortLanguageList(List<Language> langList) {
List<Language> sortedLanguages = List.from(langList);
sortedLanguages.sort((a, b) {
// German first
if (a.code == 'de') return -1;
if (b.code == 'de') return 1;
// English second
if (a.code == 'en') return -1;
if (b.code == 'en') return 1;
// All others by name ascending
return a.name.compareTo(b.name);
});
return sortedLanguages;
}

View File

@ -19,9 +19,7 @@ String ageInfo(int? birthYear) {
return ageInfo;
}
///
/// Convert decimal coordinate to degrees minutes seconds (DMS).
///
String convertDecimalToDMS(double decimalValue, {required bool isLatitude}) {
bool isNegative = decimalValue < 0;
double absoluteValue = decimalValue.abs();
@ -43,9 +41,7 @@ String convertDecimalToDMS(double decimalValue, {required bool isLatitude}) {
return '${degrees.abs()}° ${minutes.abs()}\' ${seconds.abs().toStringAsFixed(2)}" $direction';
}
///
/// Distance in kilometers between two location coordinates
///
double calculateDistance(double lat1, double lon1, double lat2, double lon2) {
const R = 6371; // earth radius in kilometers
@ -67,9 +63,7 @@ double _degreesToRadians(double degrees) {
return degrees * pi / 180;
}
///
/// Shortest distance between two users locations
///
double shortestDistanceBetweenUsers(
UserProfile currentUser, UserProfile otherUser) {
try {
@ -122,7 +116,7 @@ double calculateMatchScore(UserProfile currentUser, UserProfile otherUser) {
communicationWeight);
if (weightSum != 1) {
debugPrint(
'Warning --> calculateMatchScore : Weights Sum $weightSum != 1');
'DEBUG Info --> calculateMatchScore : Weights Sum $weightSum != 1');
}
}

View File

@ -35,51 +35,6 @@ void main() {
});
});
group('equalContent', () {
test('returns true for lists with the same content in the same order', () {
List<dynamic> list1 = [1, 2, 3];
List<dynamic> list2 = [1, 2, 3];
bool result = equalContent(list1, list2);
expect(result, true);
});
test('returns true for lists with the same content in different orders',
() {
List<dynamic> list1 = [1, 2, 3];
List<dynamic> list2 = [3, 1, 2];
bool result = equalContent(list1, list2);
expect(result, true);
});
test('returns false for lists with different content', () {
List<dynamic> list1 = [1, 2, 3];
List<dynamic> list2 = [1, 2, 4];
bool result = equalContent(list1, list2);
expect(result, false);
});
test('returns false for lists with different lengths', () {
List<dynamic> list1 = [1, 2, 3];
List<dynamic> list2 = [1, 2];
bool result = equalContent(list1, list2);
expect(result, false);
});
test('returns true for empty lists', () {
List<dynamic> list1 = [];
List<dynamic> list2 = [];
bool result = equalContent(list1, list2);
expect(result, true);
});
test('returns false for one empty and one non-empty list', () {
List<dynamic> list1 = [];
List<dynamic> list2 = [1];
bool result = equalContent(list1, list2);
expect(result, false);
});
});
group('formatTimestamp', () {
test('formatTimestamp returns the expected formatted date for 01.01.1970',
() {

View File

@ -0,0 +1,49 @@
import 'package:cofounderella/utils/list_utils.dart';
import 'package:flutter_test/flutter_test.dart';
void main() {
group('equalContent', () {
test('returns true for lists with the same content in the same order', () {
List<dynamic> list1 = [1, 2, 3];
List<dynamic> list2 = [1, 2, 3];
bool result = equalContent(list1, list2);
expect(result, true);
});
test('returns true for lists with the same content in different orders',
() {
List<dynamic> list1 = [1, 2, 3];
List<dynamic> list2 = [3, 1, 2];
bool result = equalContent(list1, list2);
expect(result, true);
});
test('returns false for lists with different content', () {
List<dynamic> list1 = [1, 2, 3];
List<dynamic> list2 = [1, 2, 4];
bool result = equalContent(list1, list2);
expect(result, false);
});
test('returns false for lists with different lengths', () {
List<dynamic> list1 = [1, 2, 3];
List<dynamic> list2 = [1, 2];
bool result = equalContent(list1, list2);
expect(result, false);
});
test('returns true for empty lists', () {
List<dynamic> list1 = [];
List<dynamic> list2 = [];
bool result = equalContent(list1, list2);
expect(result, true);
});
test('returns false for one empty and one non-empty list', () {
List<dynamic> list1 = [];
List<dynamic> list2 = [1];
bool result = equalContent(list1, list2);
expect(result, false);
});
});
}