diff --git a/android/gradle/wrapper/gradle-wrapper.properties b/android/gradle/wrapper/gradle-wrapper.properties index 7bb2df6..ac3b479 100644 --- a/android/gradle/wrapper/gradle-wrapper.properties +++ b/android/gradle/wrapper/gradle-wrapper.properties @@ -2,4 +2,4 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-8.3-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.12-all.zip diff --git a/android/settings.gradle b/android/settings.gradle index a42444d..b507b94 100644 --- a/android/settings.gradle +++ b/android/settings.gradle @@ -18,8 +18,8 @@ pluginManagement { plugins { id "dev.flutter.flutter-plugin-loader" version "1.0.0" - id "com.android.application" version "8.2.1" apply false - id "org.jetbrains.kotlin.android" version "1.8.22" apply false + id "com.android.application" version "8.9.1" apply false + id "org.jetbrains.kotlin.android" version "2.1.0" apply false } include ":app" diff --git a/assets/fonts/LoveYaLikeASister-Regular.ttf b/assets/fonts/LoveYaLikeASister-Regular.ttf deleted file mode 100644 index 8979c0f..0000000 Binary files a/assets/fonts/LoveYaLikeASister-Regular.ttf and /dev/null differ diff --git a/devtools_options.yaml b/devtools_options.yaml new file mode 100644 index 0000000..fa0b357 --- /dev/null +++ b/devtools_options.yaml @@ -0,0 +1,3 @@ +description: This file stores settings for Dart & Flutter DevTools. +documentation: https://docs.flutter.dev/tools/devtools/extensions#configure-extension-enablement-states +extensions: diff --git a/lib/features/auth/cubit/auth_cubit.dart b/lib/features/auth/cubit/auth_cubit.dart index 78c64ab..4342d72 100644 --- a/lib/features/auth/cubit/auth_cubit.dart +++ b/lib/features/auth/cubit/auth_cubit.dart @@ -7,10 +7,14 @@ part 'auth_state.dart'; class AuthCubit extends Cubit { AuthCubit() : super(AuthInitial()); - Future signIn({required String email, required String pwd}) async { + Future signIn({ + required String email, + required String pwd, + required String url, + }) async { emit(AuthLoading()); try { - await AuthRepo().signIn(email: email, pwd: pwd); + await AuthRepo().signIn(email: email, pwd: pwd, url: url); emit(AuthLoaded()); } catch (e) { emit(AuthError(message: e.toString())); diff --git a/lib/features/auth/repo/auth_repo.dart b/lib/features/auth/repo/auth_repo.dart index 1701bd3..fe0b5ea 100644 --- a/lib/features/auth/repo/auth_repo.dart +++ b/lib/features/auth/repo/auth_repo.dart @@ -7,11 +7,12 @@ import 'package:pulse/utils/utils.dart'; class AuthRepo { Future?> signIn( - {required String email, required String pwd}) async { + {required String email, required String pwd, required String url}) async { + String userKey = url == umamiUrl ? 'email' : 'username'; var res = await Requests.post( - endpoint: Endpoints.authLogin, + endpoint: '$url/auth/login', body: { - 'email': email, + userKey: email, 'password': pwd, }, noAuth: true, @@ -19,7 +20,9 @@ class AuthRepo { if (res == null) { throw Exception('Failed to sign in'); } + baseUrl = url; prefs.setString(LocalStorage.jwt, res['token']); + prefs.setString(LocalStorage.host, url); return res; } diff --git a/lib/features/auth/screens/sign_in_screen.dart b/lib/features/auth/screens/sign_in_screen.dart index 28e0e29..c3abc1f 100644 --- a/lib/features/auth/screens/sign_in_screen.dart +++ b/lib/features/auth/screens/sign_in_screen.dart @@ -4,6 +4,7 @@ import 'package:pulse/utils/colors.dart'; import 'package:pulse/utils/text.dart'; import 'package:pulse/widgets/custom_button.dart'; import 'package:pulse/widgets/custom_text_field.dart'; +import 'package:pulse/widgets/drop_down.dart'; import 'package:pulse/widgets/mordern_btn.dart'; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; @@ -24,8 +25,10 @@ class _SignInScreenState extends State { final GlobalKey formKey = GlobalKey(); TextEditingController email = TextEditingController(); TextEditingController pwd = TextEditingController(); + TextEditingController hostUrl = TextEditingController(text: umamiUrl); bool isAutoValidate = false; bool isHidden = true; + String hostType = 'Umami Cloud'; bool isValidEmail(String email) { RegExp emailRegex = RegExp(r'^[\w-\.]+@([\w-]+\.)+[\w-]{2,4}$'); @@ -66,36 +69,60 @@ class _SignInScreenState extends State { style: kTitleTextStyle.copyWith(fontSize: 30)), Text('Please sign in to continue', style: kBodyTitleTextStyle), + CustomDropDown( + removePadding: true, + selectedItem: hostType, + items: [ + 'Umami Cloud', + 'Self Hosting', + ], + hint: 'Select host type', + title: 'Host Type', + onChanged: (val) { + setState(() { + hostType = val!; + hostUrl = TextEditingController( + text: hostType == 'Umami Cloud' ? umamiUrl : ''); + }); + }, + ), CustomTextField( + data: hostUrl, preIcon: Icons.link_rounded, - hint: Endpoints.baseUrl, + hint: 'Self-hosted url', title: 'Host URL', type: TextInputType.emailAddress, - disabled: true, - // validator: (val) { - // if (val!.isEmpty) { - // return 'This field is required'; - // } else if (!isValidEmail(val)) { - // return 'Enter a valid email address'; - // } - // return null; - // }, - ), - CustomTextField( - data: email, - preIcon: Icons.email_rounded, - hint: 'Email', - title: 'Email Address', - type: TextInputType.emailAddress, + disabled: hostType == 'Umami Cloud', validator: (val) { - if (val!.isEmpty) { - return 'This field is required'; - } else if (!isValidEmail(val)) { - return 'Enter a valid email address'; + if (!(val!.startsWith('https://'))) { + return 'The url should start with https://'; } return null; }, ), + CustomTextField( + data: email, + preIcon: hostType == 'Umami Cloud' + ? Icons.email_rounded + : Icons.person_2_rounded, + hint: hostType == 'Umami Cloud' ? 'Email' : 'Username', + title: hostType == 'Umami Cloud' + ? 'Email Address' + : 'Username', + type: hostType == 'Umami Cloud' + ? TextInputType.emailAddress + : TextInputType.name, + validator: hostType == 'Umami Cloud' + ? (val) { + if (val!.isEmpty) { + return 'This field is required'; + } else if (!isValidEmail(val)) { + return 'Enter a valid email address'; + } + return null; + } + : null, + ), CustomTextField( data: pwd, preIcon: Icons.lock_rounded, @@ -134,7 +161,10 @@ class _SignInScreenState extends State { bool isValid = formKey.currentState!.validate(); if (isValid) { context.read().signIn( - email: email.text.trim(), pwd: pwd.text.trim()); + email: email.text.trim(), + pwd: pwd.text.trim(), + url: hostUrl.text.trim(), + ); } else { setState(() => isAutoValidate = true); } diff --git a/lib/features/auth/screens/splash_screen.dart b/lib/features/auth/screens/splash_screen.dart index e67a901..c0ffca5 100644 --- a/lib/features/auth/screens/splash_screen.dart +++ b/lib/features/auth/screens/splash_screen.dart @@ -1,6 +1,7 @@ import 'package:flutter/material.dart'; import 'package:in_app_update/in_app_update.dart'; import 'package:pulse/features/websites/screens/websites_screen.dart'; +import 'package:pulse/utils/endpoints.dart'; import 'package:pulse/utils/local_storage.dart'; import 'package:pulse/utils/navigation.dart'; import '../../../utils/text.dart'; @@ -20,7 +21,7 @@ class _SplashScreenState extends State { void initState() { Future.delayed(Duration(seconds: 2)).then((_) async { checkForUpdate(); - + baseUrl = prefs.getString(LocalStorage.host) ?? umamiUrl; String? token = prefs.getString(LocalStorage.jwt); Navigation.go( @@ -39,7 +40,6 @@ class _SplashScreenState extends State { message: 'New App Update Available! 🎉', context: context); } }).catchError((e) { - logger.e(e); Toast.showToast(message: e.toString(), context: context); }); } diff --git a/lib/features/sessions/repo/sessions_repo.dart b/lib/features/sessions/repo/sessions_repo.dart index c4d9467..f064339 100644 --- a/lib/features/sessions/repo/sessions_repo.dart +++ b/lib/features/sessions/repo/sessions_repo.dart @@ -19,7 +19,7 @@ class SessionsRepo { var res = await Requests.get( useKey: true, endpoint: - '${Endpoints.websites.replaceAll(Endpoints.baseUrl, 'https://api.umami.is/v1')}/$id/sessions?startAt=$startAt&endAt=$endAt&pageSize=20&page=${pageNumber ?? 1}'); + '${Endpoints.websites.replaceAll(umamiUrl, 'https://api.umami.is/v1')}/$id/sessions?startAt=$startAt&endAt=$endAt&pageSize=20&page=${pageNumber ?? 1}'); return Session.toList(res['data']); } @@ -27,7 +27,7 @@ class SessionsRepo { var res = await Requests.get( useKey: true, endpoint: - '${Endpoints.websites.replaceAll(Endpoints.baseUrl, 'https://api.umami.is/v1')}/$websiteId/sessions/$id'); + '${Endpoints.websites.replaceAll(umamiUrl, 'https://api.umami.is/v1')}/$websiteId/sessions/$id'); return Session.fromJson(res); } @@ -43,7 +43,7 @@ class SessionsRepo { var res = await Requests.get( useKey: true, endpoint: - '${Endpoints.websites.replaceAll(Endpoints.baseUrl, 'https://api.umami.is/v1')}/$websiteId/sessions/$id/activity?startAt=$startAt&endAt=$endAt'); + '${Endpoints.websites.replaceAll(umamiUrl, 'https://api.umami.is/v1')}/$websiteId/sessions/$id/activity?startAt=$startAt&endAt=$endAt'); return Event.toList(res); } } diff --git a/lib/utils/endpoints.dart b/lib/utils/endpoints.dart index 02680f8..8b7339a 100644 --- a/lib/utils/endpoints.dart +++ b/lib/utils/endpoints.dart @@ -1,5 +1,7 @@ +late String baseUrl; +String umamiUrl = 'https://cloud.umami.is/api'; + class Endpoints { - static const String baseUrl = 'https://cloud.umami.is/api'; - static const String authLogin = '$baseUrl/auth/login'; - static const String websites = '$baseUrl/websites'; + static final String authLogin = '$baseUrl/auth/login'; + static final String websites = '$baseUrl/websites'; } diff --git a/lib/utils/extensions.dart b/lib/utils/extensions.dart index f5b87d0..21fd1e3 100644 --- a/lib/utils/extensions.dart +++ b/lib/utils/extensions.dart @@ -39,7 +39,7 @@ extension StringExtensions on String { String get toBrowserIcon { if (contains('chrome')) { return Brands.chrome; - } else if (contains('ios')) { + } else if (contains('ios') || contains('safari')) { return Brands.safari; } else if (contains('opera')) { return Brands.opera; diff --git a/lib/utils/local_storage.dart b/lib/utils/local_storage.dart index ca102c5..3d6884d 100644 --- a/lib/utils/local_storage.dart +++ b/lib/utils/local_storage.dart @@ -1,3 +1,4 @@ class LocalStorage { static String jwt = 'jwt_token'; + static String host = 'host_url'; } diff --git a/lib/utils/requests.dart b/lib/utils/requests.dart index ed70ca8..8bf9817 100644 --- a/lib/utils/requests.dart +++ b/lib/utils/requests.dart @@ -138,7 +138,10 @@ class Requests { try { http.Response res; res = await fn.timeout(const Duration(seconds: 10)); - + logger.i(endpoint); + if (endpoint.contains('auth/login') && res.statusCode == 404) { + return throw Exception('Invalid host url'); + } if (res.statusCode != okStatusCode) { return throw Exception( json.decode(res.body)?['error'] ?? 'Error occurred'); diff --git a/lib/widgets/drop_down.dart b/lib/widgets/drop_down.dart index 16738b4..c04ab5e 100644 --- a/lib/widgets/drop_down.dart +++ b/lib/widgets/drop_down.dart @@ -47,7 +47,7 @@ class CustomDropDown extends StatelessWidget { ), Container( margin: EdgeInsets.only( - bottom: removePadding ? 0 : 15.0, top: title == null ? 0.0 : 8.0), + bottom: removePadding ? 0 : 15.0, top: title == null ? 0.0 : 0.0), padding: const EdgeInsets.symmetric(horizontal: 10), height: maxHeight, decoration: BoxDecoration( diff --git a/pubspec.lock b/pubspec.lock index ca061e5..9840183 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -133,10 +133,10 @@ packages: dependency: transitive description: name: characters - sha256: "04a925763edad70e8443c99234dc3328f442e811f1d8fd1a72f1c8ad0f69a605" + sha256: f71061c654a3380576a52b451dd5532377954cf9dbd272a78fc8479606670803 url: "https://pub.dev" source: hosted - version: "1.3.0" + version: "1.4.0" checked_yaml: dependency: transitive description: @@ -149,10 +149,10 @@ packages: dependency: transitive description: name: clock - sha256: cb6d7f03e1de671e34607e909a7213e31d7752be4fb66a86d29fe1eb14bfb5cf + sha256: fddb70d9b5277016c77a80201021d40a2247104d9f4aa7bab7157b7e3f05b84b url: "https://pub.dev" source: hosted - version: "1.1.1" + version: "1.1.2" code_builder: dependency: transitive description: @@ -165,10 +165,10 @@ packages: dependency: transitive description: name: collection - sha256: a1ace0a119f20aabc852d165077c036cd864315bd99b7eaa10a60100341941bf + sha256: "2f5709ae4d3d59dd8f7cd309b4e023046b57d8a6c82130785d2b0e5868084e76" url: "https://pub.dev" source: hosted - version: "1.19.0" + version: "1.19.1" convert: dependency: transitive description: @@ -237,10 +237,10 @@ packages: dependency: transitive description: name: fake_async - sha256: "511392330127add0b769b75a987850d136345d9227c6b94c96a04cf4a391bf78" + sha256: "5368f224a74523e8d2e7399ea1638b37aecfca824a3cc4dfdf77bf1fa905ac44" url: "https://pub.dev" source: hosted - version: "1.3.1" + version: "1.3.3" ffi: dependency: transitive description: @@ -476,26 +476,26 @@ packages: dependency: transitive description: name: leak_tracker - sha256: "7bb2830ebd849694d1ec25bf1f44582d6ac531a57a365a803a6034ff751d2d06" + sha256: "8dcda04c3fc16c14f48a7bb586d4be1f0d1572731b6d81d51772ef47c02081e0" url: "https://pub.dev" source: hosted - version: "10.0.7" + version: "11.0.1" leak_tracker_flutter_testing: dependency: transitive description: name: leak_tracker_flutter_testing - sha256: "9491a714cca3667b60b5c420da8217e6de0d1ba7a5ec322fab01758f6998f379" + sha256: "1dbc140bb5a23c75ea9c4811222756104fbcd1a27173f0c34ca01e16bea473c1" url: "https://pub.dev" source: hosted - version: "3.0.8" + version: "3.0.10" leak_tracker_testing: dependency: transitive description: name: leak_tracker_testing - sha256: "6ba465d5d76e67ddf503e1161d1f4a6bc42306f9d66ca1e8f079a47290fb06d3" + sha256: "8d5a2d49f4a66b49744b23b018848400d23e54caf9463f4eb20df3eb8acb2eb1" url: "https://pub.dev" source: hosted - version: "3.0.1" + version: "3.0.2" lints: dependency: transitive description: @@ -524,10 +524,10 @@ packages: dependency: transitive description: name: matcher - sha256: d2323aa2060500f906aa31a895b4030b6da3ebdcc5619d14ce1aada65cd161cb + sha256: dc58c723c3c24bf8d3e2d3ad3f2f9d7bd9cf43ec6feaa64181775e60190153f2 url: "https://pub.dev" source: hosted - version: "0.12.16+1" + version: "0.12.17" material_color_utilities: dependency: transitive description: @@ -540,10 +540,10 @@ packages: dependency: transitive description: name: meta - sha256: bdb68674043280c3428e9ec998512fb681678676b3c54e773629ffe74419f8c7 + sha256: e3641ec5d63ebf0d9b41bd43201a66e3fc79a65db5f61fc181f04cd27aab950c url: "https://pub.dev" source: hosted - version: "1.15.0" + version: "1.16.0" mime: dependency: transitive description: @@ -588,10 +588,10 @@ packages: dependency: transitive description: name: path - sha256: "087ce49c3f0dc39180befefc60fdb4acd8f8620e5682fe2476afd0b3688bb4af" + sha256: "75cca69d1490965be98c73ceaea117e8a04dd21217b37b292c9ddbec0d955bc5" url: "https://pub.dev" source: hosted - version: "1.9.0" + version: "1.9.1" path_parsing: dependency: transitive description: @@ -793,18 +793,18 @@ packages: dependency: transitive description: name: stack_trace - sha256: "9f47fd3630d76be3ab26f0ee06d213679aa425996925ff3feffdec504931c377" + sha256: "8b27215b45d22309b5cddda1aa2b19bdfec9df0e765f2de506401c071d38d1b1" url: "https://pub.dev" source: hosted - version: "1.12.0" + version: "1.12.1" stream_channel: dependency: transitive description: name: stream_channel - sha256: ba2aa5d8cc609d96bbb2899c28934f9e1af5cddbd60a827822ea467161eb54e7 + sha256: "969e04c80b8bcdf826f8f16579c7b14d780458bd97f56d107d3950fdbeef059d" url: "https://pub.dev" source: hosted - version: "2.1.2" + version: "2.1.4" stream_transform: dependency: transitive description: @@ -833,10 +833,10 @@ packages: dependency: transitive description: name: test_api - sha256: "664d3a9a64782fcdeb83ce9c6b39e78fd2971d4e37827b9b06c3aa1edc5e760c" + sha256: "522f00f556e73044315fa4585ec3270f1808a4b186c936e612cab0b565ff1e00" url: "https://pub.dev" source: hosted - version: "0.7.3" + version: "0.7.6" timeago: dependency: "direct main" description: @@ -961,10 +961,10 @@ packages: dependency: transitive description: name: vector_math - sha256: "80b3257d1492ce4d091729e3a67a60407d227c27241d6927be0130c98e741803" + sha256: d530bd74fea330e6e364cda7a85019c434070188383e1cd8d9777ee586914c5b url: "https://pub.dev" source: hosted - version: "2.1.4" + version: "2.2.0" vm_service: dependency: transitive description: @@ -1038,5 +1038,5 @@ packages: source: hosted version: "3.1.3" sdks: - dart: ">=3.6.1 <4.0.0" + dart: ">=3.8.0-0 <4.0.0" flutter: ">=3.27.0" diff --git a/pubspec.yaml b/pubspec.yaml index c7cac43..b40fc20 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -4,7 +4,7 @@ description: "A new Flutter project." # pub.dev using `flutter pub publish`. This is preferred for private packages. publish_to: "none" # Remove this line if you wish to publish to pub.dev -version: 1.0.4+5 +version: 1.0.6+7 environment: sdk: ^3.6.1