diff --git a/.github/workflows/build-flutter.yml b/.github/workflows/build-flutter.yml
index 8a3d0e07d6..7732c9d947 100644
--- a/.github/workflows/build-flutter.yml
+++ b/.github/workflows/build-flutter.yml
@@ -7,13 +7,14 @@ jobs:
runs-on: macos-latest
steps:
- - uses: actions/checkout@v2
- - uses: actions/setup-java@v1
+ - uses: actions/checkout@v3
+ - uses: actions/setup-java@v2
with:
- java-version: '12.x'
- - uses: subosito/flutter-action@v1
+ distribution: 'zulu'
+ java-version: '11'
+ - uses: subosito/flutter-action@v2
with:
- flutter-version: '2.10.4'
+ flutter-version: '3.3.9'
- run: flutter pub get
- - run: flutter analyze
+ - run: flutter analyze --no-fatal-infos
- run: flutter build apk
diff --git a/README.md b/README.md
index 506e13e170..73ce81dbee 100644
--- a/README.md
+++ b/README.md
@@ -1,6 +1,6 @@
-
![](./assets/logo_white.png)
+
diff --git a/android/app/build.gradle b/android/app/build.gradle
index 86b65d6249..6c7287f552 100644
--- a/android/app/build.gradle
+++ b/android/app/build.gradle
@@ -39,6 +39,7 @@ android {
targetSdkVersion 30
versionCode flutterVersionCode.toInteger()
versionName flutterVersionName
+ vectorDrawables.useSupportLibrary = true
}
buildTypes {
diff --git a/android/app/src/main/res/drawable-v21/launch_background.xml b/android/app/src/main/res/drawable-v21/launch_background.xml
index b5ab9b06a0..3120564598 100644
--- a/android/app/src/main/res/drawable-v21/launch_background.xml
+++ b/android/app/src/main/res/drawable-v21/launch_background.xml
@@ -4,9 +4,7 @@
- -
-
-
+
diff --git a/android/app/src/main/res/drawable/bugheist_logo.xml b/android/app/src/main/res/drawable/bugheist_logo.xml
new file mode 100644
index 0000000000..f0a5b1773e
--- /dev/null
+++ b/android/app/src/main/res/drawable/bugheist_logo.xml
@@ -0,0 +1,15 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/android/app/src/main/res/drawable/launch_background.xml b/android/app/src/main/res/drawable/launch_background.xml
index b5ab9b06a0..3120564598 100644
--- a/android/app/src/main/res/drawable/launch_background.xml
+++ b/android/app/src/main/res/drawable/launch_background.xml
@@ -4,9 +4,7 @@
- -
-
-
+
diff --git a/android/app/src/main/res/drawable/launch_image.png b/android/app/src/main/res/drawable/launch_image.png
deleted file mode 100644
index 4a5c80c7ae..0000000000
Binary files a/android/app/src/main/res/drawable/launch_image.png and /dev/null differ
diff --git a/assets/bugheist_logo.png b/assets/bugheist_logo.png
deleted file mode 100644
index 0a506f169f..0000000000
Binary files a/assets/bugheist_logo.png and /dev/null differ
diff --git a/assets/bugheist_logo.svg b/assets/bugheist_logo.svg
new file mode 100644
index 0000000000..828c278bc0
--- /dev/null
+++ b/assets/bugheist_logo.svg
@@ -0,0 +1,14 @@
+
diff --git a/assets/logo_white.png b/assets/logo_white.png
deleted file mode 100644
index 5a457016e1..0000000000
Binary files a/assets/logo_white.png and /dev/null differ
diff --git a/assets/logo_white.svg b/assets/logo_white.svg
new file mode 100644
index 0000000000..635a91fe4b
--- /dev/null
+++ b/assets/logo_white.svg
@@ -0,0 +1,14 @@
+
diff --git a/lib/src/app.dart b/lib/src/app.dart
index 114a83af5d..85ba5552dc 100644
--- a/lib/src/app.dart
+++ b/lib/src/app.dart
@@ -91,7 +91,7 @@ class BugHeistState extends State {
primaryColor: Colors.white,
visualDensity: VisualDensity.adaptivePlatformDensity,
),
- home: WelcomePage(),
+ home: Scaffold(body: WelcomePage()),
),
),
);
diff --git a/lib/src/components/appbar.dart b/lib/src/components/appbar.dart
index 257536ad19..5e8f2a8cb9 100644
--- a/lib/src/components/appbar.dart
+++ b/lib/src/components/appbar.dart
@@ -1,12 +1,14 @@
-import 'package:bugheist/src/components/searchbar.dart';
-import 'package:bugheist/src/routes/routing.dart';
import 'package:flutter/material.dart';
+import 'package:flutter_svg/flutter_svg.dart';
+
+import '../routes/routing.dart';
+import '../components/searchbar.dart';
/// The app's main Appbar
AppBar buildAppBar({required BuildContext context}) {
return AppBar(
- title: Image.asset(
- 'assets/bugheist_logo.png',
+ title: SvgPicture.asset(
+ 'assets/bugheist_logo.svg',
fit: BoxFit.cover,
height: 30,
),
diff --git a/lib/src/components/issue_intro_card.dart b/lib/src/components/issue_intro_card.dart
index 3bd317936b..f56e06bcda 100644
--- a/lib/src/components/issue_intro_card.dart
+++ b/lib/src/components/issue_intro_card.dart
@@ -1,8 +1,9 @@
-import 'package:bugheist/src/routes/routing.dart';
import 'package:flutter/material.dart';
import 'package:google_fonts/google_fonts.dart';
+import '../routes/routing.dart';
import '../models/issue_model.dart';
+import '../components/issuelike.dart';
/// The card used to display issues in the list of issues on the Issue Page.
class IssueCard extends StatelessWidget {
@@ -35,67 +36,120 @@ class IssueCard extends StatelessWidget {
children: [
Container(
width: size.width,
- height: 0.2 * size.height,
+ height: 0.214 * size.height,
child: Image.network(
- issue.screenshotLink!,
+ issue.screenshotsLink![0],
fit: BoxFit.fill,
),
),
Container(
width: size.width,
- padding: const EdgeInsets.symmetric(horizontal: 12),
- child: Column(
- crossAxisAlignment: CrossAxisAlignment.start,
- children: [
- Container(
- padding: const EdgeInsets.only(top: 12),
- child: Text(
- "Issue #${issue.id}",
- overflow: TextOverflow.ellipsis,
- softWrap: true,
- style: GoogleFonts.ubuntu(
- textStyle: TextStyle(
- color: Color(0xFFDC4654),
- fontSize: 17.5,
+ height: 0.12 * size.height,
+ padding: const EdgeInsets.symmetric(
+ horizontal: 12,
+ vertical: 12,
+ ),
+ child: ListTile(
+ // contentPadding: EdgeInsets.zero,
+ isThreeLine: true,
+ title: Text(
+ "Issue #${issue.id}",
+ overflow: TextOverflow.ellipsis,
+ softWrap: true,
+ style: GoogleFonts.ubuntu(
+ textStyle: TextStyle(
+ color: Color(0xFFDC4654),
+ fontSize: 17.5,
+ ),
+ fontWeight: FontWeight.bold,
+ ),
+ ),
+ subtitle: Column(
+ crossAxisAlignment: CrossAxisAlignment.start,
+ children: [
+ Container(
+ padding: const EdgeInsets.only(top: 8, bottom: 12),
+ child: Text(
+ issue.description.replaceAll("\n", " "),
+ overflow: TextOverflow.ellipsis,
+ softWrap: true,
+ style: GoogleFonts.aBeeZee(
+ textStyle: TextStyle(
+ fontSize: 12,
+ color: Color(0xFF737373),
+ ),
),
- fontWeight: FontWeight.bold,
),
),
- ),
- Container(
- padding: const EdgeInsets.only(top: 8, bottom: 12),
- child: Text(
- issue.description.replaceAll("\n", " "),
+ Text(
+ issue.created_date,
overflow: TextOverflow.ellipsis,
softWrap: true,
style: GoogleFonts.aBeeZee(
textStyle: TextStyle(
- fontSize: 12,
- color: Color(0xFF737373),
+ fontSize: 10,
+ color: Color(0xFFA3A3A3),
),
),
),
- ),
- Row(
- children: [
- Container(
- padding: const EdgeInsets.only(bottom: 12),
- child: Text(
- issue.created_date,
- overflow: TextOverflow.ellipsis,
- softWrap: true,
- style: GoogleFonts.aBeeZee(
- textStyle: TextStyle(
- fontSize: 10,
- color: Color(0xFFA3A3A3),
- ),
- ),
- ),
- ),
- ],
- ),
- ],
+ ],
+ ),
+ trailing: IssueLikeButton(
+ issue: issue,
+ ),
),
+ // child: Column(
+ // crossAxisAlignment: CrossAxisAlignment.start,
+ // children: [
+ // Container(
+ // padding: const EdgeInsets.only(top: 12),
+ // child: Text(
+ // "Issue #${issue.id}",
+ // overflow: TextOverflow.ellipsis,
+ // softWrap: true,
+ // style: GoogleFonts.ubuntu(
+ // textStyle: TextStyle(
+ // color: Color(0xFFDC4654),
+ // fontSize: 17.5,
+ // ),
+ // fontWeight: FontWeight.bold,
+ // ),
+ // ),
+ // ),
+ // Container(
+ // padding: const EdgeInsets.only(top: 8, bottom: 12),
+ // child: Text(
+ // issue.description.replaceAll("\n", " "),
+ // overflow: TextOverflow.ellipsis,
+ // softWrap: true,
+ // style: GoogleFonts.aBeeZee(
+ // textStyle: TextStyle(
+ // fontSize: 12,
+ // color: Color(0xFF737373),
+ // ),
+ // ),
+ // ),
+ // ),
+ // Row(
+ // children: [
+ // Container(
+ // padding: const EdgeInsets.only(bottom: 12),
+ // child: Text(
+ // issue.created_date,
+ // overflow: TextOverflow.ellipsis,
+ // softWrap: true,
+ // style: GoogleFonts.aBeeZee(
+ // textStyle: TextStyle(
+ // fontSize: 10,
+ // color: Color(0xFFA3A3A3),
+ // ),
+ // ),
+ // ),
+ // ),
+ // ],
+ // ),
+ // ],
+ // ),
)
],
),
diff --git a/lib/src/components/issueflag.dart b/lib/src/components/issueflag.dart
new file mode 100644
index 0000000000..40f359250b
--- /dev/null
+++ b/lib/src/components/issueflag.dart
@@ -0,0 +1,87 @@
+import 'package:flutter/material.dart';
+import 'package:flutter_riverpod/flutter_riverpod.dart';
+
+import '../models/issue_model.dart';
+import '../providers/login_provider.dart';
+import '../util/api/issues_api.dart';
+import '../util/enums/login_type.dart';
+
+/// Issue flags show and toggle component.
+class IssueFlagButton extends ConsumerStatefulWidget {
+ final Issue issue;
+ final Color? color;
+ const IssueFlagButton({
+ Key? key,
+ this.color,
+ required this.issue,
+ }) : super(key: key);
+
+ @override
+ ConsumerState createState() =>
+ _IssueFlagButtonState();
+}
+
+class _IssueFlagButtonState extends ConsumerState {
+ late int flags;
+ late bool flagged;
+
+ Future toggleIssueFlag() async {
+ setState(() {
+ if (flagged) {
+ flags = flags - 1;
+ } else {
+ flags = flags + 1;
+ }
+ flagged = !flagged;
+ });
+
+ try {
+ bool status = await IssueApiClient.toggleIssueLikes(widget.issue.id!);
+ if (!status) {
+ setState(() {
+ flags = widget.issue.flags!;
+ flagged = widget.issue.flagged!;
+ });
+ } else {
+ widget.issue.likes = flags;
+ widget.issue.flagged = flagged;
+ }
+ } catch (e) {
+ print(e);
+ }
+ }
+
+ @override
+ void initState() {
+ super.initState();
+ flagged = widget.issue.flagged!;
+ flags = widget.issue.flags!;
+ }
+
+ @override
+ Widget build(BuildContext context) {
+ return TextButton.icon(
+ onPressed: () async {
+ if (ref.read(loginProvider.notifier).loginType == LoginType.guest) {
+ ScaffoldMessenger.of(context).showSnackBar(
+ SnackBar(
+ content: Text("Login to like and flag issues!"),
+ ),
+ );
+ } else {
+ await toggleIssueFlag();
+ }
+ },
+ icon: Icon(
+ Icons.flag_outlined,
+ color: widget.color,
+ ),
+ label: Text(
+ "${flags}",
+ style: TextStyle(
+ color: widget.color,
+ ),
+ ),
+ );
+ }
+}
diff --git a/lib/src/components/issuelike.dart b/lib/src/components/issuelike.dart
new file mode 100644
index 0000000000..83e03f2e8a
--- /dev/null
+++ b/lib/src/components/issuelike.dart
@@ -0,0 +1,88 @@
+import 'package:flutter/material.dart';
+import 'package:flutter_riverpod/flutter_riverpod.dart';
+
+import '../models/issue_model.dart';
+import '../util/api/issues_api.dart';
+import '../util/enums/login_type.dart';
+import '../providers/login_provider.dart';
+
+/// Issue likes show and toggle component.
+class IssueLikeButton extends ConsumerStatefulWidget {
+ final Issue issue;
+ final Color? color;
+
+ const IssueLikeButton({
+ Key? key,
+ required this.issue,
+ this.color,
+ }) : super(key: key);
+
+ @override
+ ConsumerState createState() =>
+ _IssueLikeButtonState();
+}
+
+class _IssueLikeButtonState extends ConsumerState {
+ late int likes;
+ late bool liked;
+
+ Future toggleIssueLike() async {
+ setState(() {
+ if (liked) {
+ likes = likes - 1;
+ } else {
+ likes = likes + 1;
+ }
+ liked = !liked;
+ });
+
+ try {
+ bool status = await IssueApiClient.toggleIssueLikes(widget.issue.id!);
+ if (!status) {
+ setState(() {
+ likes = widget.issue.likes!;
+ liked = widget.issue.liked!;
+ });
+ } else {
+ widget.issue.likes = likes;
+ widget.issue.liked = liked;
+ }
+ } catch (e) {
+ print(e);
+ }
+ }
+
+ @override
+ void initState() {
+ super.initState();
+ liked = widget.issue.liked!;
+ likes = widget.issue.likes!;
+ }
+
+ @override
+ Widget build(BuildContext context) {
+ return TextButton.icon(
+ onPressed: () async {
+ if (ref.read(loginProvider.notifier).loginType == LoginType.guest) {
+ ScaffoldMessenger.of(context).showSnackBar(
+ SnackBar(
+ content: Text("Login to like and flag issues!"),
+ ),
+ );
+ } else {
+ await toggleIssueLike();
+ }
+ },
+ icon: Icon(
+ (liked) ? Icons.favorite : Icons.favorite_border_rounded,
+ color: widget.color,
+ ),
+ label: Text(
+ "$likes",
+ style: TextStyle(
+ color: widget.color,
+ ),
+ ),
+ );
+ }
+}
diff --git a/lib/src/components/searchbar.dart b/lib/src/components/searchbar.dart
index b01eccb05e..28a86a0e9d 100644
--- a/lib/src/components/searchbar.dart
+++ b/lib/src/components/searchbar.dart
@@ -1,9 +1,9 @@
-import 'package:bugheist/src/components/issue_intro_card.dart';
import 'package:flutter/material.dart';
import 'package:google_fonts/google_fonts.dart';
-import '../models/issuedata_model.dart';
import '../util/api/issues_api.dart';
+import '../models/issuedata_model.dart';
+import '../components/issue_intro_card.dart';
/// The search bar of app for searching issues based on keyword.
class BugHeistSearchDelegate extends SearchDelegate {
diff --git a/lib/src/models/issue_model.dart b/lib/src/models/issue_model.dart
index 0cd7652be8..ead212950c 100644
--- a/lib/src/models/issue_model.dart
+++ b/lib/src/models/issue_model.dart
@@ -14,7 +14,11 @@ class Issue {
final bool isOpen;
final String? userAgent;
final String ocr;
- final String? screenshotLink;
+ int? likes;
+ int? flags;
+ bool? liked;
+ bool? flagged;
+ final List? screenshotsLink;
final DateTime? closedDate;
final String? githubUrl;
final DateTime? created;
@@ -50,7 +54,11 @@ class Issue {
required this.isOpen,
this.userAgent,
required this.ocr,
- this.screenshotLink,
+ this.likes,
+ this.flags,
+ this.liked,
+ this.flagged,
+ this.screenshotsLink,
this.closedDate,
this.githubUrl,
this.created,
@@ -77,7 +85,11 @@ class Issue {
isOpen: (responseData["status"] == "open") ? true : false,
userAgent: responseData["user_agent"],
ocr: responseData["ocr"],
- screenshotLink: responseData["screenshot"],
+ likes: responseData["upvotes"],
+ flags: responseData["flags"],
+ liked: responseData["upvotted"],
+ flagged: responseData["flagged"],
+ screenshotsLink: responseData["screenshots"].cast(),
closedDate: (responseData["closed_date"].runtimeType != Null)
? DateTime.parse(responseData["closed_date"])
: null,
diff --git a/lib/src/models/user_model.dart b/lib/src/models/user_model.dart
index 89d3c6e060..6a1975bf8f 100644
--- a/lib/src/models/user_model.dart
+++ b/lib/src/models/user_model.dart
@@ -42,6 +42,7 @@ class User {
}
}
+/// The [User] instance used for anonymous login.
User guestUser = User(
id: 1234567890,
username: "Anonymous",
diff --git a/lib/src/pages/auth/forgot.dart b/lib/src/pages/auth/forgot.dart
index 8940764fb7..6a8f5ef1e4 100644
--- a/lib/src/pages/auth/forgot.dart
+++ b/lib/src/pages/auth/forgot.dart
@@ -1,5 +1,6 @@
import 'package:bugheist/src/util/api/auth_api.dart';
import 'package:flutter/material.dart';
+import 'package:flutter_svg/flutter_svg.dart';
import 'package:google_fonts/google_fonts.dart';
/// Page for initiating the process for recovering
@@ -67,9 +68,10 @@ class _ForgotPasswordPageState extends State {
vertical: 0.1 * size.height,
),
child: Center(
- child: Image.asset(
- 'assets/logo_white.png',
+ child: SvgPicture.asset(
+ 'assets/logo_white.svg',
fit: BoxFit.contain,
+ height: 192.0,
),
),
),
diff --git a/lib/src/pages/auth/login.dart b/lib/src/pages/auth/login.dart
index 2764b4073e..30a0797594 100644
--- a/lib/src/pages/auth/login.dart
+++ b/lib/src/pages/auth/login.dart
@@ -2,6 +2,7 @@ import 'package:bugheist/src/providers/authstate_provider.dart';
import 'package:bugheist/src/routes/routing.dart';
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
+import 'package:flutter_svg/flutter_svg.dart';
import 'package:google_fonts/google_fonts.dart';
/// The login page for the app.
@@ -49,9 +50,10 @@ class _LoginPageState extends State {
vertical: 0.075 * size.height,
),
child: Center(
- child: Image.asset(
- 'assets/logo_white.png',
+ child: SvgPicture.asset(
+ 'assets/logo_white.svg',
fit: BoxFit.contain,
+ height: 192.0,
),
),
),
@@ -109,6 +111,7 @@ class LoginForm extends ConsumerStatefulWidget {
class _LoginFormState extends ConsumerState {
bool showPassword = false;
bool isShowVisible = false;
+ bool rememberMe = false;
final _formKey = GlobalKey();
late TextEditingController _userController;
late TextEditingController _passwordController;
@@ -123,6 +126,7 @@ class _LoginFormState extends ConsumerState {
"username": username,
"password": password,
},
+ rememberMe,
parentContext,
);
}
@@ -212,6 +216,27 @@ class _LoginFormState extends ConsumerState {
obscureText: !showPassword,
),
),
+ SizedBox(
+ height: 0.005 * widget.size.height,
+ ),
+ SizedBox(
+ width: 0.8 * widget.size.width,
+ child: Row(
+ children: [
+ Checkbox(
+ value: rememberMe,
+ onChanged: (value) {
+ setState(() {
+ rememberMe = !rememberMe;
+ });
+ },
+ ),
+ Text(
+ "Remember me",
+ ),
+ ],
+ ),
+ ),
SizedBox(
height: 0.025 * widget.size.height,
),
diff --git a/lib/src/pages/auth/signup.dart b/lib/src/pages/auth/signup.dart
index ef24c0dabc..7f09998bd4 100644
--- a/lib/src/pages/auth/signup.dart
+++ b/lib/src/pages/auth/signup.dart
@@ -1,5 +1,6 @@
import 'package:bugheist/src/util/api/auth_api.dart';
import 'package:flutter/material.dart';
+import 'package:flutter_svg/flutter_svg.dart';
import 'package:google_fonts/google_fonts.dart';
import '../../routes/routing.dart';
@@ -49,9 +50,10 @@ class _SignUpPageState extends State {
vertical: 0.085 * size.height,
),
child: Center(
- child: Image.asset(
- 'assets/logo_white.png',
+ child: SvgPicture.asset(
+ 'assets/logo_white.svg',
fit: BoxFit.contain,
+ height: 192.0,
),
),
),
diff --git a/lib/src/pages/drawer/about.dart b/lib/src/pages/drawer/about.dart
index e9df542819..fc8506afed 100644
--- a/lib/src/pages/drawer/about.dart
+++ b/lib/src/pages/drawer/about.dart
@@ -1,5 +1,6 @@
import 'package:bugheist/src/constants/about_constants.dart';
import 'package:flutter/material.dart';
+import 'package:flutter_svg/flutter_svg.dart';
import 'package:google_fonts/google_fonts.dart';
/// Page for describing the BugHeist project.
@@ -28,8 +29,8 @@ class AboutPage extends StatelessWidget {
children: [
Container(
padding: EdgeInsets.fromLTRB(0, 36, 0, 24),
- child: Image.asset(
- 'assets/bugheist_logo.png',
+ child: SvgPicture.asset(
+ 'assets/bugheist_logo.svg',
width: 169.42,
),
),
diff --git a/lib/src/pages/drawer/legal.dart b/lib/src/pages/drawer/legal.dart
index 30e6fab6ca..4f04468a6b 100644
--- a/lib/src/pages/drawer/legal.dart
+++ b/lib/src/pages/drawer/legal.dart
@@ -1,5 +1,6 @@
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
+import 'package:flutter_svg/flutter_svg.dart';
import 'package:google_fonts/google_fonts.dart';
import 'package:flutter_markdown/flutter_markdown.dart';
@@ -30,8 +31,8 @@ class LegalPage extends StatelessWidget {
children: [
Container(
padding: EdgeInsets.fromLTRB(0, 36, 0, 24),
- child: Image.asset(
- 'assets/bugheist_logo.png',
+ child: SvgPicture.asset(
+ 'assets/bugheist_logo.svg',
width: 169.42,
),
),
diff --git a/lib/src/pages/home/feed.dart b/lib/src/pages/home/feed.dart
index 9ec70b9dc0..5887268136 100644
--- a/lib/src/pages/home/feed.dart
+++ b/lib/src/pages/home/feed.dart
@@ -1,8 +1,8 @@
-import 'package:bugheist/src/global/variables.dart';
import 'package:flutter/material.dart';
-import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:google_fonts/google_fonts.dart';
-// import '../../providers/issuelist_provider.dart';
+import 'package:flutter_riverpod/flutter_riverpod.dart';
+
+import '../../global/variables.dart';
/// Page for showing social activity of BugHeist, new issues,
/// top premium subscribers.
diff --git a/lib/src/pages/home/home.dart b/lib/src/pages/home/home.dart
index 71c66aee52..8506b00424 100644
--- a/lib/src/pages/home/home.dart
+++ b/lib/src/pages/home/home.dart
@@ -90,6 +90,10 @@ class _HomeState extends ConsumerState {
await ref.read(authStateNotifier.notifier).logout();
}
+ Future forgetUser() async {
+ await ref.read(authStateNotifier.notifier).forgetUser();
+ }
+
@override
void initState() {
_selectedIndex = widget.startingIndex ?? 0;
@@ -143,6 +147,7 @@ class _HomeState extends ConsumerState {
// Update trhe state of the app
// ...
// Then close the drawer
+ await forgetUser();
Navigator.pushReplacementNamed(
context,
RouteManager.welcomePage,
diff --git a/lib/src/pages/home/issues.dart b/lib/src/pages/home/issues.dart
index c022ef9340..b88f0a5aa1 100644
--- a/lib/src/pages/home/issues.dart
+++ b/lib/src/pages/home/issues.dart
@@ -62,7 +62,7 @@ class IssuesPageState extends ConsumerState
return Scaffold(
body: RefreshIndicator(
onRefresh: () async {
- ref.refresh(issueListProvider);
+ ref.read(issueListProvider.notifier).refreshIssueList();
},
child: SingleChildScrollView(
child: Column(
@@ -109,7 +109,7 @@ class IssuesPageState extends ConsumerState
if (issueList!.isEmpty) {
return Center(
child: Text(
- "Looks like you don't have any todos :) \n Yay!",
+ "Looks like there aren't many bugs :) \n Yay!",
textAlign: TextAlign.center,
),
);
diff --git a/lib/src/pages/home/leaderboard.dart b/lib/src/pages/home/leaderboard.dart
index ade8248b16..292609156f 100644
--- a/lib/src/pages/home/leaderboard.dart
+++ b/lib/src/pages/home/leaderboard.dart
@@ -1,10 +1,10 @@
-import 'package:bugheist/src/util/api/api.dart';
-import 'package:flutter/material.dart';
-import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'dart:async';
+import 'package:flutter/material.dart';
+import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:google_fonts/google_fonts.dart';
+import '../../util/api/api.dart';
import '../../routes/routing.dart';
/// The Leaderboards dashboard page, contains the Global,
diff --git a/lib/src/pages/home/profile.dart b/lib/src/pages/home/profile.dart
index d1faff8b82..b4a7260095 100644
--- a/lib/src/pages/home/profile.dart
+++ b/lib/src/pages/home/profile.dart
@@ -1,14 +1,14 @@
-import 'package:bugheist/src/global/variables.dart';
-import 'package:bugheist/src/util/api/issues_api.dart';
import 'package:flutter/material.dart';
-import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:google_fonts/google_fonts.dart';
+import 'package:flutter_riverpod/flutter_riverpod.dart';
-import '../../components/issuechip.dart';
-import '../../models/issue_model.dart';
+import '../../routes/routing.dart';
+import '../../global/variables.dart';
import '../../models/user_model.dart';
+import '../../models/issue_model.dart';
+import '../../util/api/issues_api.dart';
+import '../../components/issuechip.dart';
import '../../providers/authstate_provider.dart';
-import '../../routes/routing.dart';
/// Page that displays the stats of a user registered on BugHeist,
/// shows dummy data for Guest login.
diff --git a/lib/src/pages/home/report_bug.dart b/lib/src/pages/home/report_bug.dart
index ddcd62a5c2..2722fbf5f3 100644
--- a/lib/src/pages/home/report_bug.dart
+++ b/lib/src/pages/home/report_bug.dart
@@ -2,15 +2,15 @@
import 'dart:io';
import 'dart:ui';
-import 'package:bugheist/src/global/variables.dart';
-import 'package:bugheist/src/pages/home/start_hunt.dart';
-import 'package:bugheist/src/providers/login_provider.dart';
-import 'package:bugheist/src/util/api/issues_api.dart';
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:google_fonts/google_fonts.dart';
import 'package:image_picker/image_picker.dart';
+import '../../pages/home/start_hunt.dart';
+import '../../global/variables.dart';
+import '../../providers/login_provider.dart';
+import '../../util/api/issues_api.dart';
import '../../models/issue_model.dart';
import '../../util/enums/login_type.dart';
diff --git a/lib/src/pages/issues/issue_detail.dart b/lib/src/pages/issues/issue_detail.dart
index 93796de88c..5930912afa 100644
--- a/lib/src/pages/issues/issue_detail.dart
+++ b/lib/src/pages/issues/issue_detail.dart
@@ -1,8 +1,11 @@
-import 'package:bugheist/src/components/issuechip.dart';
-import 'package:bugheist/src/models/issue_model.dart';
import 'package:flutter/material.dart';
import 'package:google_fonts/google_fonts.dart';
+import '../../models/issue_model.dart';
+import '../../components/issuechip.dart';
+import '../../components/issueflag.dart';
+import '../../components/issuelike.dart';
+
/// Popup page when an issue is clicked to be viewed.
class IssueDetailPage extends StatelessWidget {
static final String path = "lib/src/pages/blog/article1.dart";
@@ -28,21 +31,30 @@ class IssueDetailPage extends StatelessWidget {
},
),
title: Text("Issue #${issue.id}"),
+ actions: [
+ IssueLikeButton(
+ issue: issue,
+ color: Colors.white,
+ ),
+ IssueFlagButton(
+ issue: issue,
+ color: Colors.white,
+ ),
+ ],
backgroundColor: Color(0xFFDC4654),
),
body: SingleChildScrollView(
+ padding: EdgeInsets.all(20),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Container(
- padding: EdgeInsets.symmetric(horizontal: 20),
width: size.width,
color: Theme.of(context).canvasColor,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Container(
- padding: EdgeInsets.fromLTRB(0, 12, 0, 0),
child: Text(
"Issue #${issue.id}",
style: GoogleFonts.ubuntu(
@@ -78,19 +90,18 @@ class IssueDetailPage extends StatelessWidget {
),
),
Container(
- height: 0.334 * size.height,
width: size.width,
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(10),
),
padding: const EdgeInsets.symmetric(horizontal: 20),
child: Image.network(
- issue.screenshotLink!,
+ issue.screenshotsLink![0],
fit: BoxFit.fill,
),
),
Container(
- padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 12),
+ padding: const EdgeInsets.symmetric(vertical: 12),
child: Text(
"Description",
overflow: TextOverflow.ellipsis,
@@ -105,7 +116,6 @@ class IssueDetailPage extends StatelessWidget {
),
),
Container(
- padding: const EdgeInsets.symmetric(horizontal: 20),
child: Text(
issue.description,
),
diff --git a/lib/src/pages/welcome.dart b/lib/src/pages/welcome.dart
index 940a991b40..42ea761821 100644
--- a/lib/src/pages/welcome.dart
+++ b/lib/src/pages/welcome.dart
@@ -1,5 +1,6 @@
import 'package:bugheist/src/providers/authstate_provider.dart';
import 'package:flutter/material.dart';
+import 'package:flutter_svg/flutter_svg.dart';
import 'package:google_fonts/google_fonts.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
@@ -7,9 +8,24 @@ import '../routes/routing.dart';
/// The Landing page for unauthenticated users, or if a
/// user wants to try the guest mode of the app.
-class WelcomePage extends StatelessWidget {
+class WelcomePage extends ConsumerStatefulWidget {
const WelcomePage({Key? key}) : super(key: key);
+ @override
+ ConsumerState createState() => _WelcomePageState();
+}
+
+class _WelcomePageState extends ConsumerState {
+
+ @override
+ void initState() {
+ super.initState();
+ WidgetsBinding.instance.addPostFrameCallback((_) async {
+ await ref.read(authStateNotifier.notifier).loadUserIfRemembered(context);
+ });
+ }
+
+
@override
Widget build(BuildContext context) {
final Size size = MediaQuery.of(context).size;
@@ -32,9 +48,10 @@ class WelcomePage extends StatelessWidget {
vertical: 0.1 * size.height,
),
child: Center(
- child: Image.asset(
- 'assets/logo_white.png',
+ child: SvgPicture.asset(
+ 'assets/logo_white.svg',
fit: BoxFit.contain,
+ height: 192.0,
),
),
),
diff --git a/lib/src/providers/authstate_provider.dart b/lib/src/providers/authstate_provider.dart
index 2c1c9f81de..bfaf42bd6b 100644
--- a/lib/src/providers/authstate_provider.dart
+++ b/lib/src/providers/authstate_provider.dart
@@ -1,6 +1,7 @@
import 'package:bugheist/src/util/api/user_api.dart';
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
+import 'package:flutter_secure_storage/flutter_secure_storage.dart';
import './login_provider.dart';
import '../models/user_model.dart';
@@ -19,6 +20,7 @@ final authStateNotifier =
class AuthNotifier extends StateNotifier> {
final Reader read;
AsyncValue? previousState;
+ final storage = new FlutterSecureStorage();
AuthNotifier(this.read, [AsyncValue? authstate])
: super(
@@ -32,9 +34,63 @@ class AuthNotifier extends StateNotifier> {
read(loginProvider.notifier).setGuestLogin();
}
+ Future loadUserIfRemembered(BuildContext context) async {
+ String? username = await storage.read(key: "username");
+ String? password = await storage.read(key: "password");
+
+ if (username == null || password == null) {
+ return false;
+ }
+
+ SnackBar authSnack = SnackBar(
+ content: Text("Authenticating ..."),
+ duration: Duration(minutes: 1),
+ );
+ ScaffoldMessenger.of(context).showSnackBar(authSnack);
+
+ Map userCreds = {
+ "username": username,
+ "password": password
+ };
+ state = AsyncValue.data(AuthState.authenticating);
+ try {
+ User? authenticatedUser = await AuthApiClient.login(userCreds);
+ if (authenticatedUser != null) {
+ currentUser = authenticatedUser;
+ await UserApiClient.getUserInfo(currentUser!);
+
+ state = AsyncValue.data(AuthState.loggedIn);
+ read(loginProvider.notifier).setUserLogin();
+ Navigator.of(context).pushNamed(
+ RouteManager.homePage,
+ );
+ ScaffoldMessenger.of(context).clearSnackBars();
+ }
+ } catch (e) {}
+ return true;
+ }
+
+ Future rememberUser(
+ Map userCreds
+ ) async {
+ await storage.write(
+ key: "username",
+ value: userCreds["username"],
+ );
+ await storage.write(
+ key: "password",
+ value: userCreds["password"],
+ );
+ }
+
+ Future forgetUser() async {
+ await storage.delete(key: "username");
+ await storage.delete(key: "password");
+ }
+
/// Do a user type authentication.
Future userLogin(
- Map userCreds, BuildContext parentContext) async {
+ Map userCreds, bool rememberMe, BuildContext parentContext) async {
state = AsyncValue.data(AuthState.authenticating);
SnackBar authSnack = SnackBar(
content: Text("Authenticating ..."),
@@ -49,7 +105,9 @@ class AuthNotifier extends StateNotifier> {
state = AsyncValue.data(AuthState.loggedIn);
read(loginProvider.notifier).setUserLogin();
-
+ if (rememberMe) {
+ rememberUser(userCreds);
+ }
ScaffoldMessenger.of(parentContext).clearSnackBars();
Navigator.of(parentContext).pushReplacementNamed(
RouteManager.homePage,
diff --git a/lib/src/providers/issuelist_provider.dart b/lib/src/providers/issuelist_provider.dart
index 55f453ba84..a4d732786f 100644
--- a/lib/src/providers/issuelist_provider.dart
+++ b/lib/src/providers/issuelist_provider.dart
@@ -54,6 +54,20 @@ class IssueListProvider extends StateNotifier?>?> {
}
}
+ /// Function call for refreshing state.
+ Future refreshIssueList() async {
+ state = AsyncValue.loading();
+ try {
+ final IssueData? issueData = await IssueApiClient.getAllIssues(
+ IssueEndPoints.issues,
+ );
+ nxtUrl = issueData!.nextQuery;
+ state = AsyncValue.data(issueData.issueList);
+ } catch (e) {
+ AsyncValue.error(e);
+ }
+ }
+
/// Caches the current state to prevent errors.
void _cacheState() {
previousState = state;
diff --git a/lib/src/util/api/auth_api.dart b/lib/src/util/api/auth_api.dart
index c542a35b6f..da756fdf3b 100644
--- a/lib/src/util/api/auth_api.dart
+++ b/lib/src/util/api/auth_api.dart
@@ -5,6 +5,8 @@ import 'package:bugheist/src/util/endpoints/auth_endpoints.dart';
import 'package:flutter/material.dart';
import 'package:http/http.dart' as http;
+import '../../global/variables.dart';
+
/// Class for accessing the auth client.
class AuthApiClient {
AuthApiClient._();
@@ -39,6 +41,9 @@ class AuthApiClient {
try {
response = await http.post(
Uri.parse(AuthEndPoints.logout),
+ headers: {
+ "Authorization": "Token ${currentUser!.token!}",
+ },
);
print(response.statusCode);
if (response.statusCode == 200) isLoggedOut = true;
diff --git a/lib/src/util/api/issues_api.dart b/lib/src/util/api/issues_api.dart
index 868ddd83a9..23577cd0aa 100644
--- a/lib/src/util/api/issues_api.dart
+++ b/lib/src/util/api/issues_api.dart
@@ -1,4 +1,6 @@
import 'dart:convert';
+import 'package:bugheist/src/global/variables.dart';
+import 'package:bugheist/src/models/user_model.dart';
import 'package:bugheist/src/routes/routing.dart';
import 'package:bugheist/src/util/endpoints/issue_endpoints.dart';
import 'package:flutter/material.dart';
@@ -15,8 +17,16 @@ class IssueApiClient {
http.Response? response;
IssueData? issueData;
List? issueList;
+ Map? headers = (currentUser != guestUser)
+ ? {
+ "Authorization": "Token ${currentUser!.token!}",
+ }
+ : null;
try {
- response = await http.get(Uri.parse(paginatedUrl));
+ response = await http.get(
+ Uri.parse(paginatedUrl),
+ headers: headers,
+ );
if (response.statusCode == 200) {
issueList = [];
var decodedResponse = jsonDecode(utf8.decode(response.bodyBytes));
@@ -145,6 +155,86 @@ class IssueApiClient {
}
}
+ static Future getIssueLikes(int id) async {
+ http.Response? response;
+ int? likeCount;
+ try {
+ response = await http.get(
+ Uri.parse(IssueEndPoints.likeIssues + "$id/"),
+ headers: {
+ "Authorization": "Token ${currentUser!.token!}",
+ },
+ );
+ if (response.statusCode == 200) {
+ var decodedResponse = jsonDecode(utf8.decode(response.bodyBytes));
+ likeCount = decodedResponse["likes"];
+ }
+ } catch (e) {
+ print(e);
+ }
+ return likeCount;
+ }
+
+ static Future toggleIssueLikes(int id) async {
+ http.Response? response;
+ bool toggleSuccess = false;
+ try {
+ response = await http.post(
+ Uri.parse(IssueEndPoints.likeIssues + "$id/"),
+ headers: {
+ "Authorization": "Token ${currentUser!.token!}",
+ },
+ );
+ print(response.statusCode);
+ if (response.statusCode == 200) {
+ toggleSuccess = true;
+ }
+ } catch (e) {
+ print(e);
+ }
+ return toggleSuccess;
+ }
+
+ static Future getIssueFlag(int id) async {
+ http.Response? response;
+ int? flagCount;
+ try {
+ response = await http.get(
+ Uri.parse(IssueEndPoints.flagIssues + "$id/"),
+ headers: {
+ "Authorization": "Token ${currentUser!.token!}",
+ },
+ );
+ if (response.statusCode == 200) {
+ var decodedResponse = jsonDecode(utf8.decode(response.bodyBytes));
+ flagCount = decodedResponse["flags"];
+ }
+ } catch (e) {
+ print("e");
+ }
+ return flagCount;
+ }
+
+ static Future toggleIssueFlag(int id) async {
+ http.Response? response;
+ bool toggleSuccess = false;
+ try {
+ response = await http.post(
+ Uri.parse(IssueEndPoints.flagIssues + "$id/"),
+ headers: {
+ "Authorization": "Token ${currentUser!.token!}",
+ },
+ );
+ print(response.statusCode);
+ if (response.statusCode == 200) {
+ toggleSuccess = true;
+ }
+ } catch (e) {
+ print(e);
+ }
+ return toggleSuccess;
+ }
+
static Future getAllUserIssues() async {}
static Future getUserIssueById() async {}
diff --git a/lib/src/util/endpoints/auth_endpoints.dart b/lib/src/util/endpoints/auth_endpoints.dart
index 0007ae47b9..dff0170a46 100644
--- a/lib/src/util/endpoints/auth_endpoints.dart
+++ b/lib/src/util/endpoints/auth_endpoints.dart
@@ -2,13 +2,15 @@
class AuthEndPoints {
AuthEndPoints._();
- static const String baseUrl = "https://www.bugheist.com/auth/";
+ static const String baseUrl = "https://www.bugheist.com/";
- static const String emailpasswordLogin = baseUrl + "login/";
+ static const String baseUrl2 = "https://www.bugheist.com/auth/";
- static const String logout = baseUrl + "logout/";
+ static const String emailpasswordLogin = baseUrl + "authenticate/";
- static const String register = baseUrl + "registration/";
+ static const String logout = baseUrl2 + "logout/";
- static const String reset = baseUrl + "password/reset/";
+ static const String register = baseUrl2 + "registration/";
+
+ static const String reset = baseUrl2 + "password/reset/";
}
diff --git a/lib/src/util/endpoints/issue_endpoints.dart b/lib/src/util/endpoints/issue_endpoints.dart
index 550131d4b9..653e5fa3d1 100644
--- a/lib/src/util/endpoints/issue_endpoints.dart
+++ b/lib/src/util/endpoints/issue_endpoints.dart
@@ -4,7 +4,11 @@ class IssueEndPoints {
static const String baseUrl = "https://www.bugheist.com/api/v1/";
- static const issues = baseUrl + "issues/";
+ static const String issues = baseUrl + "issues/";
- static const userIssues = baseUrl + "userissues/";
+ static const String userIssues = baseUrl + "userissues/";
+
+ static const String likeIssues = baseUrl + "issue/like/";
+
+ static const String flagIssues = baseUrl + "issue/flag/";
}
diff --git a/windows/flutter/generated_plugins.cmake b/windows/flutter/generated_plugins.cmake
index 69b5ae4f58..4fc759c48f 100644
--- a/windows/flutter/generated_plugins.cmake
+++ b/windows/flutter/generated_plugins.cmake
@@ -6,6 +6,9 @@ list(APPEND FLUTTER_PLUGIN_LIST
flutter_secure_storage_windows
)
+list(APPEND FLUTTER_FFI_PLUGIN_LIST
+)
+
set(PLUGIN_BUNDLED_LIBRARIES)
foreach(plugin ${FLUTTER_PLUGIN_LIST})
@@ -14,3 +17,8 @@ foreach(plugin ${FLUTTER_PLUGIN_LIST})
list(APPEND PLUGIN_BUNDLED_LIBRARIES $)
list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${plugin}_bundled_libraries})
endforeach(plugin)
+
+foreach(ffi_plugin ${FLUTTER_FFI_PLUGIN_LIST})
+ add_subdirectory(flutter/ephemeral/.plugin_symlinks/${ffi_plugin}/windows plugins/${ffi_plugin})
+ list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${ffi_plugin}_bundled_libraries})
+endforeach(ffi_plugin)