Skip to content

Commit

Permalink
feat: add competition banner on home page (#8)
Browse files Browse the repository at this point in the history
* feat: add competition banner on home page
* feat: add query intent for url launcher in android
* fix: dart formatting
  • Loading branch information
Sandip Pramanik authored Aug 7, 2024
1 parent edb3e2a commit 31a0483
Show file tree
Hide file tree
Showing 13 changed files with 274 additions and 2 deletions.
7 changes: 7 additions & 0 deletions android/app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
@@ -1,5 +1,12 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android" package="dev.thecodexhub.sudoku">
<uses-permission android:name="android.permission.INTERNET" />
<queries>
<intent>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="https" />
</intent>
</queries>
<application android:label="${appName}" android:name="${applicationName}" android:icon="@mipmap/ic_launcher">
<activity android:name=".MainActivity" android:exported="true" android:launchMode="singleTask" android:theme="@style/LaunchTheme" android:configChanges="orientation|keyboardHidden|keyboard|screenSize|smallestScreenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode" android:hardwareAccelerated="true" android:windowSoftInputMode="adjustResize">
<!-- Specifies an Android theme to apply to this Activity as soon as
Expand Down
12 changes: 12 additions & 0 deletions ios/Podfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -1009,6 +1009,8 @@ PODS:
- nanopb (< 2.30911.0, >= 2.30908.0)
- FirebaseSharedSwift (10.29.0)
- Flutter (1.0.0)
- flutter_native_splash (0.0.1):
- Flutter
- GoogleUtilities/AppDelegateSwizzler (7.13.3):
- GoogleUtilities/Environment
- GoogleUtilities/Logger
Expand Down Expand Up @@ -1124,14 +1126,18 @@ PODS:
- shared_preferences_foundation (0.0.1):
- Flutter
- FlutterMacOS
- url_launcher_ios (0.0.1):
- Flutter

DEPENDENCIES:
- cloud_firestore (from `.symlinks/plugins/cloud_firestore/ios`)
- firebase_auth (from `.symlinks/plugins/firebase_auth/ios`)
- firebase_core (from `.symlinks/plugins/firebase_core/ios`)
- Flutter (from `Flutter`)
- flutter_native_splash (from `.symlinks/plugins/flutter_native_splash/ios`)
- path_provider_foundation (from `.symlinks/plugins/path_provider_foundation/darwin`)
- shared_preferences_foundation (from `.symlinks/plugins/shared_preferences_foundation/darwin`)
- url_launcher_ios (from `.symlinks/plugins/url_launcher_ios/ios`)

SPEC REPOS:
trunk:
Expand Down Expand Up @@ -1164,10 +1170,14 @@ EXTERNAL SOURCES:
:path: ".symlinks/plugins/firebase_core/ios"
Flutter:
:path: Flutter
flutter_native_splash:
:path: ".symlinks/plugins/flutter_native_splash/ios"
path_provider_foundation:
:path: ".symlinks/plugins/path_provider_foundation/darwin"
shared_preferences_foundation:
:path: ".symlinks/plugins/shared_preferences_foundation/darwin"
url_launcher_ios:
:path: ".symlinks/plugins/url_launcher_ios/ios"

SPEC CHECKSUMS:
abseil: d121da9ef7e2ff4cab7666e76c5a3e0915ae08c3
Expand All @@ -1185,6 +1195,7 @@ SPEC CHECKSUMS:
FirebaseFirestoreInternal: f43d25cc04835ec3aa1885f4fc946a1a4f9e1c56
FirebaseSharedSwift: 20530f495084b8d840f78a100d8c5ee613375f6e
Flutter: e0871f40cf51350855a761d2e70bf5af5b9b5de7
flutter_native_splash: edf599c81f74d093a4daf8e17bd7a018854bc778
GoogleUtilities: ea963c370a38a8069cc5f7ba4ca849a60b6d7d15
"gRPC-C++": e725ef63c4475d7cdb7e2cf16eb0fde84bd9ee51
gRPC-Core: eee4be35df218649fe66d721a05a7f27a28f069b
Expand All @@ -1195,6 +1206,7 @@ SPEC CHECKSUMS:
PromisesObjC: f5707f49cb48b9636751c5b2e7d227e43fba9f47
RecaptchaInterop: 7d1a4a01a6b2cb1610a47ef3f85f0c411434cb21
shared_preferences_foundation: fcdcbc04712aee1108ac7fda236f363274528f78
url_launcher_ios: 5334b05cef931de560670eeae103fd3e431ac3fe

PODFILE CHECKSUM: a57f30d18f102dd3ce366b1d62a55ecbef2158e5

Expand Down
10 changes: 8 additions & 2 deletions lib/home/view/home_page.dart
Original file line number Diff line number Diff line change
Expand Up @@ -129,9 +129,15 @@ class HomeViewLayout extends StatelessWidget {
),
HeaderSection(),
ResponsiveGap(
small: 36,
small: 24,
medium: 48,
large: 96,
large: 40,
),
CompetitionBanner(),
ResponsiveGap(
small: 24,
medium: 48,
large: 56,
),
ResponsiveHomePageLayout(
highlightedSection: HighlightedSection(),
Expand Down
65 changes: 65 additions & 0 deletions lib/home/widgets/competition_banner.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
import 'package:flutter/material.dart';
import 'package:sudoku/colors/colors.dart';
import 'package:sudoku/l10n/l10n.dart';
import 'package:sudoku/typography/typography.dart';
import 'package:sudoku/utilities/utilities.dart';

/// The url for the Gemini API Competition.
const _competitionUrl = 'https://ai.google.dev/competition';

/// {@template competition_banner}
/// Displays a banner in the Home Page that launches the Gemini
/// API Competition details website.
/// {@endtemplate}
class CompetitionBanner extends StatelessWidget {
/// {@macro competition_banner}
const CompetitionBanner({super.key});

static const gradient = LinearGradient(
colors: [SudokuColors.darkPurple, SudokuColors.darkPink],
begin: Alignment.bottomLeft,
end: Alignment.topRight,
);

@override
Widget build(BuildContext context) {
final l10n = context.l10n;
return OutlinedButton(
onPressed: () => openLink(Uri.parse(_competitionUrl)),
style: OutlinedButton.styleFrom(
padding: const EdgeInsets.fromLTRB(24, 0, 24, 2.5),
),
child: Text.rich(
TextSpan(
children: [
WidgetSpan(
child: Text(
l10n.competitionBannerText,
style: SudokuTextStyle.caption.copyWith(
fontWeight: SudokuFontWeight.medium,
fontSize: 12,
),
),
),
WidgetSpan(
child: ShaderMask(
blendMode: BlendMode.srcIn,
shaderCallback: (bounds) => gradient.createShader(
Rect.fromLTWH(0, 0, bounds.width, bounds.height),
),
child: Text(
l10n.competitionBannerCta,
style: SudokuTextStyle.caption.copyWith(
fontWeight: SudokuFontWeight.semiBold,
fontSize: 12,
),
),
),
),
],
),
textAlign: TextAlign.center,
),
);
}
}
1 change: 1 addition & 0 deletions lib/home/widgets/widgets.dart
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
export 'competition_banner.dart';
export 'player_info.dart';
8 changes: 8 additions & 0 deletions lib/l10n/arb/app_en.arb
Original file line number Diff line number Diff line change
Expand Up @@ -153,5 +153,13 @@
"gameOverReason3": " in this puzzle.",
"@gameOverReason3": {
"description": "Third part of the game over reason for Puzzle Page"
},
"competitionBannerText": "Built for the Gemini API Competition. ",
"@competitionBannerText": {
"description": "Text show in Competition Banner in the Home Page"
},
"competitionBannerCta": "Read More -->",
"@competitionBannerCta": {
"description": "Text shown as the CTA for Competition Banner in the Home Page"
}
}
11 changes: 11 additions & 0 deletions lib/utilities/link_helper.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import 'package:flutter/widgets.dart';
import 'package:url_launcher/url_launcher.dart';

/// Opens the given [url] in a new tab of the host browser
Future<void> openLink(Uri url, {VoidCallback? onError}) async {
if (await canLaunchUrl(url)) {
await launchUrl(url);
} else if (onError != null) {
onError();
}
}
1 change: 1 addition & 0 deletions lib/utilities/utilities.dart
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
export 'difficulty_extension.dart';
export 'link_helper.dart';
export 'time_formatter.dart';
64 changes: 64 additions & 0 deletions pubspec.lock
Original file line number Diff line number Diff line change
Expand Up @@ -1074,6 +1074,70 @@ packages:
url: "https://pub.dev"
source: hosted
version: "2.2.2"
url_launcher:
dependency: "direct main"
description:
name: url_launcher
sha256: "21b704ce5fa560ea9f3b525b43601c678728ba46725bab9b01187b4831377ed3"
url: "https://pub.dev"
source: hosted
version: "6.3.0"
url_launcher_android:
dependency: transitive
description:
name: url_launcher_android
sha256: "94d8ad05f44c6d4e2ffe5567ab4d741b82d62e3c8e288cc1fcea45965edf47c9"
url: "https://pub.dev"
source: hosted
version: "6.3.8"
url_launcher_ios:
dependency: transitive
description:
name: url_launcher_ios
sha256: e43b677296fadce447e987a2f519dcf5f6d1e527dc35d01ffab4fff5b8a7063e
url: "https://pub.dev"
source: hosted
version: "6.3.1"
url_launcher_linux:
dependency: transitive
description:
name: url_launcher_linux
sha256: ab360eb661f8879369acac07b6bb3ff09d9471155357da8443fd5d3cf7363811
url: "https://pub.dev"
source: hosted
version: "3.1.1"
url_launcher_macos:
dependency: transitive
description:
name: url_launcher_macos
sha256: "9a1a42d5d2d95400c795b2914c36fdcb525870c752569438e4ebb09a2b5d90de"
url: "https://pub.dev"
source: hosted
version: "3.2.0"
url_launcher_platform_interface:
dependency: "direct dev"
description:
name: url_launcher_platform_interface
sha256: "552f8a1e663569be95a8190206a38187b531910283c3e982193e4f2733f01029"
url: "https://pub.dev"
source: hosted
version: "2.3.2"
url_launcher_web:
dependency: transitive
description:
name: url_launcher_web
sha256: "772638d3b34c779ede05ba3d38af34657a05ac55b06279ea6edd409e323dca8e"
url: "https://pub.dev"
source: hosted
version: "2.3.3"
url_launcher_windows:
dependency: transitive
description:
name: url_launcher_windows
sha256: "49c10f879746271804767cb45551ec5592cdab00ee105c06dddde1a98f73b185"
url: "https://pub.dev"
source: hosted
version: "3.1.2"
vector_math:
dependency: transitive
description:
Expand Down
2 changes: 2 additions & 0 deletions pubspec.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ dependencies:
loading_indicator: ^3.1.1
rxdart: ^0.27.1
shared_preferences: ^2.2.3
url_launcher: ^6.3.0

dev_dependencies:
bloc_test: ^9.1.6
Expand All @@ -44,6 +45,7 @@ dev_dependencies:
mockingjay: ^0.5.0
mocktail: ^1.0.3
plugin_platform_interface: ^2.1.8
url_launcher_platform_interface: ^2.3.2
very_good_analysis: ^6.0.0

flutter:
Expand Down
46 changes: 46 additions & 0 deletions test/helpers/link_helper_test.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
// ignore_for_file: prefer_const_constructors

import 'package:flutter_test/flutter_test.dart';
import 'package:mocktail/mocktail.dart';
import 'package:sudoku/utilities/utilities.dart';
import 'package:url_launcher_platform_interface/url_launcher_platform_interface.dart';

import 'helpers.dart';

class _FakeLaunchOptions extends Fake implements LaunchOptions {}

void main() {
late UrlLauncherPlatform urlLauncher;

setUp(() {
urlLauncher = MockUrlLauncher();
UrlLauncherPlatform.instance = urlLauncher;
});

setUpAll(() {
registerFallbackValue(_FakeLaunchOptions());
});

group('openLink', () {
final uri = Uri.parse('uri');
final url = uri.toString();

test('launches the link', () async {
when(() => urlLauncher.canLaunch(url)).thenAnswer((_) async => true);
when(() => urlLauncher.launchUrl(any(), any()))
.thenAnswer((_) async => true);
await openLink(uri);
verify(
() => urlLauncher.launchUrl(url, any()),
).called(1);
});

test('executes the onError callback when it cannot launch', () async {
var wasCalled = false;
when(() => urlLauncher.canLaunch(url)).thenAnswer((_) async => false);
await openLink(uri, onError: () => wasCalled = true);

await expectLater(wasCalled, isTrue);
});
});
}
5 changes: 5 additions & 0 deletions test/helpers/mocks.dart
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import 'package:sudoku/puzzle/puzzle.dart';
import 'package:sudoku/repository/repository.dart';
import 'package:sudoku/storage/storage.dart';
import 'package:sudoku/timer/timer.dart';
import 'package:url_launcher_platform_interface/url_launcher_platform_interface.dart';

class MockSudoku extends Mock implements Sudoku {}

Expand Down Expand Up @@ -70,3 +71,7 @@ class MockPlayerRepository extends Mock implements PlayerRepository {}
class MockUser extends Mock implements User {}

class MockPlayer extends Mock implements Player {}

class MockUrlLauncher extends Mock
with MockPlatformInterfaceMixin
implements UrlLauncherPlatform {}
44 changes: 44 additions & 0 deletions test/home/widgets/competition_banner_test.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
// ignore_for_file: prefer_const_constructors

import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:mocktail/mocktail.dart';
import 'package:sudoku/home/home.dart';
import 'package:url_launcher_platform_interface/url_launcher_platform_interface.dart';

import '../../helpers/helpers.dart';

class _FakeLaunchOptions extends Fake implements LaunchOptions {}

void main() {
group('CompetitionBanner', () {
late UrlLauncherPlatform urlLauncher;

setUp(() {
urlLauncher = MockUrlLauncher();
UrlLauncherPlatform.instance = urlLauncher;

when(() => urlLauncher.canLaunch(any())).thenAnswer((_) async => true);
when(() => urlLauncher.launchUrl(any(), any()))
.thenAnswer((_) async => true);
});

setUpAll(() {
registerFallbackValue(_FakeLaunchOptions());
});

testWidgets('renders an [OutlinedButton]', (tester) async {
await tester.pumpApp(CompetitionBanner());
expect(find.byType(OutlinedButton), findsOneWidget);
});

testWidgets('opens a link when tapped', (tester) async {
await tester.pumpApp(CompetitionBanner());
await tester.tap(find.byType(OutlinedButton));

verify(
() => urlLauncher.launchUrl(any(), any()),
).called(1);
});
});
}

0 comments on commit 31a0483

Please sign in to comment.