diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml
index d9d4ab5..968087e 100644
--- a/android/app/src/main/AndroidManifest.xml
+++ b/android/app/src/main/AndroidManifest.xml
@@ -34,6 +34,16 @@
+
+
+
+
+
+
+
+
diff --git a/assets/images/user.png b/assets/images/user.png
index 50a8ec0..22dc0b0 100644
Binary files a/assets/images/user.png and b/assets/images/user.png differ
diff --git a/ios/Runner.xcodeproj/project.pbxproj b/ios/Runner.xcodeproj/project.pbxproj
index d29fcc9..72baf42 100644
--- a/ios/Runner.xcodeproj/project.pbxproj
+++ b/ios/Runner.xcodeproj/project.pbxproj
@@ -36,6 +36,7 @@
22230FEC84FAAF7E8AA781A7 /* Pods-Runner.release-development.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.release-development.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.release-development.xcconfig"; sourceTree = ""; };
230C707E8D6A9E6A1DC18028 /* Pods-Runner.release-staging.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.release-staging.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.release-staging.xcconfig"; sourceTree = ""; };
2D785F1A266C2C4E00E43AE3 /* config */ = {isa = PBXFileReference; lastKnownFileType = folder; path = config; sourceTree = ""; };
+ 2DBBAF542734E8E30043162B /* Runner.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = Runner.entitlements; sourceTree = ""; };
3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = ""; };
4019077285505CC6EADC6A13 /* Pods_Runner.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Runner.framework; sourceTree = BUILT_PRODUCTS_DIR; };
451976F66DC4F31C5F96421B /* Pods-Runner.profile-development.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.profile-development.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.profile-development.xcconfig"; sourceTree = ""; };
@@ -103,6 +104,7 @@
97C146F01CF9000F007C117D /* Runner */ = {
isa = PBXGroup;
children = (
+ 2DBBAF542734E8E30043162B /* Runner.entitlements */,
97C146FA1CF9000F007C117D /* Main.storyboard */,
97C146FD1CF9000F007C117D /* Assets.xcassets */,
97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */,
@@ -408,6 +410,7 @@
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CLANG_ENABLE_MODULES = YES;
+ CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements;
CODE_SIGN_IDENTITY = "iPhone Distribution";
CODE_SIGN_STYLE = Manual;
CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
@@ -545,6 +548,7 @@
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CLANG_ENABLE_MODULES = YES;
+ CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements;
CODE_SIGN_IDENTITY = "iPhone Distribution";
CODE_SIGN_STYLE = Manual;
CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
@@ -574,6 +578,7 @@
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CLANG_ENABLE_MODULES = YES;
+ CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements;
CODE_SIGN_IDENTITY = "iPhone Distribution";
CODE_SIGN_STYLE = Manual;
CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
@@ -658,6 +663,7 @@
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CLANG_ENABLE_MODULES = YES;
+ CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements;
CODE_SIGN_IDENTITY = "iPhone Distribution";
CODE_SIGN_STYLE = Manual;
CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
@@ -740,6 +746,7 @@
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CLANG_ENABLE_MODULES = YES;
+ CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements;
CODE_SIGN_IDENTITY = "iPhone Distribution";
CODE_SIGN_STYLE = Manual;
CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
@@ -819,6 +826,7 @@
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CLANG_ENABLE_MODULES = YES;
+ CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements;
CODE_SIGN_IDENTITY = "iPhone Distribution";
CODE_SIGN_STYLE = Manual;
CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
@@ -903,6 +911,7 @@
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CLANG_ENABLE_MODULES = YES;
+ CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements;
CODE_SIGN_IDENTITY = "iPhone Distribution";
CODE_SIGN_STYLE = Manual;
CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
@@ -985,6 +994,7 @@
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CLANG_ENABLE_MODULES = YES;
+ CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements;
CODE_SIGN_IDENTITY = "iPhone Distribution";
CODE_SIGN_STYLE = Manual;
CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
@@ -1064,6 +1074,7 @@
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CLANG_ENABLE_MODULES = YES;
+ CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements;
CODE_SIGN_IDENTITY = "iPhone Distribution";
CODE_SIGN_STYLE = Manual;
CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
diff --git a/ios/Runner/Runner.entitlements b/ios/Runner/Runner.entitlements
new file mode 100644
index 0000000..0d677e6
--- /dev/null
+++ b/ios/Runner/Runner.entitlements
@@ -0,0 +1,10 @@
+
+
+
+
+ com.apple.developer.associated-domains
+
+ applinks:spotvideo.app
+
+
+
diff --git a/ios/fastlane/metadata/en-US/release_notes.txt b/ios/fastlane/metadata/en-US/release_notes.txt
index a2de267..81f8952 100644
--- a/ios/fastlane/metadata/en-US/release_notes.txt
+++ b/ios/fastlane/metadata/en-US/release_notes.txt
@@ -1,2 +1,2 @@
-- Nearby videos will be clustered together
-- Sharing videos will now share spot web page
\ No newline at end of file
+- Fixed a bug where clicking on shared video link does not open the video page within the app
+- Updated the defualt user icon
\ No newline at end of file
diff --git a/lib/app/app.dart b/lib/app/app.dart
index 69ea87f..402fc8d 100644
--- a/lib/app/app.dart
+++ b/lib/app/app.dart
@@ -7,6 +7,7 @@ import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:flutter_localizations/flutter_localizations.dart';
import 'package:flutter_secure_storage/flutter_secure_storage.dart';
import 'package:spot/cubits/notification/notification_cubit.dart';
+import 'package:spot/data_profiders/app_link_provider.dart';
import 'package:spot/data_profiders/location_provider.dart';
import 'package:spot/pages/tab_page.dart';
import 'package:spot/repositories/repository.dart';
@@ -86,7 +87,9 @@ class App extends StatelessWidget {
navigatorObservers: [
FirebaseAnalyticsObserver(analytics: _analytics),
],
- home: TabPage(),
+ home: TabPage(
+ appLinkProvider: AppLinkProvider(),
+ ),
),
),
);
diff --git a/lib/data_profiders/app_link_provider.dart b/lib/data_profiders/app_link_provider.dart
new file mode 100644
index 0000000..2f39978
--- /dev/null
+++ b/lib/data_profiders/app_link_provider.dart
@@ -0,0 +1,32 @@
+import 'package:flutter/material.dart';
+import 'package:spot/pages/view_video_page.dart';
+import 'package:spot/utils/constants.dart';
+import 'package:uni_links/uni_links.dart';
+
+/// Takes care of app links
+class AppLinkProvider {
+ /// Sets up the receiver of app links
+ Future setupAppLinks(BuildContext context) async {
+ /// Quick and dirty way of
+ /// waiting until TabPage gets added to the widget tree.
+ await Future.delayed(const Duration(milliseconds: 300));
+
+ try {
+ final initialUri = await getInitialUri();
+ _handleAppLink(uri: initialUri, context: context);
+ uriLinkStream.listen((uri) => _handleAppLink(uri: uri, context: context));
+ } catch (_) {
+ context.showErrorSnackbar('Error opening the video.');
+ }
+ }
+
+ void _handleAppLink({required Uri? uri, required BuildContext context}) {
+ if (uri != null) {
+ final path = uri.path;
+ if (path.split('/').first == 'post') {
+ final videoId = path.split('/').last;
+ Navigator.of(context).push(ViewVideoPage.route(videoId: videoId));
+ }
+ }
+ }
+}
diff --git a/lib/pages/tab_page.dart b/lib/pages/tab_page.dart
index 41166be..1b310e5 100644
--- a/lib/pages/tab_page.dart
+++ b/lib/pages/tab_page.dart
@@ -7,6 +7,7 @@ import 'package:spot/components/gradient_border.dart';
import 'package:spot/components/gradient_button.dart';
import 'package:spot/components/notification_dot.dart';
import 'package:spot/cubits/notification/notification_cubit.dart';
+import 'package:spot/data_profiders/app_link_provider.dart';
import 'package:spot/pages/record_page.dart';
import 'package:spot/pages/tabs/map_tab.dart';
import 'package:spot/pages/tabs/notifications_tab.dart';
@@ -22,16 +23,13 @@ import 'record_page.dart';
/// Page that holds tab navigation at the bottom.
/// This is the first page presented to the user.
class TabPage extends StatefulWidget {
- /// Name of this page within `RouteSettinngs`
- static const name = 'TabPage';
+ /// Page that holds tab navigation at the bottom.
+ /// This is the first page presented to the user.
+ const TabPage({Key? key, required AppLinkProvider appLinkProvider})
+ : _appLinkProvider = appLinkProvider,
+ super(key: key);
- /// Method ot create this page with necessary `BlocProvider`
- static Route route() {
- return MaterialPageRoute(
- settings: const RouteSettings(name: name),
- builder: (_) => TabPage(),
- );
- }
+ final AppLinkProvider _appLinkProvider;
@override
TabPageState createState() => TabPageState();
@@ -196,6 +194,7 @@ class TabPageState extends State {
@override
void initState() {
super.initState();
+ widget._appLinkProvider.setupAppLinks(context);
WidgetsBinding.instance
?.addObserver(LifecycleEventHandler(resumeCallBack: onResumed));
}
diff --git a/lib/pages/view_video_page.dart b/lib/pages/view_video_page.dart
index d3f4e6c..55dfcb2 100644
--- a/lib/pages/view_video_page.dart
+++ b/lib/pages/view_video_page.dart
@@ -21,7 +21,6 @@ import 'package:spot/utils/functions.dart';
import 'package:video_player/video_player.dart';
import '../utils/constants.dart';
-import 'tab_page.dart';
@visibleForTesting
@@ -412,9 +411,8 @@ class __DeletingDialogContentState extends State<_DeletingDialogContent> {
_loading = true;
});
await widget._videoCubit.delete();
- Navigator.of(context).popUntil(
- (route) => route.settings.name == TabPage.name,
- );
+ Navigator.of(context)
+ .popUntil((route) => route.isFirst);
} catch (err) {
setState(() {
_loading = false;
@@ -482,9 +480,8 @@ class __BlockingDialogContentState extends State<_BlockingDialogContent> {
_loading = true;
});
await widget._videoCubit.block(widget._blockedUserId);
- Navigator.of(context).popUntil(
- (route) => route.settings.name == TabPage.name,
- );
+ Navigator.of(context)
+ .popUntil((route) => route.isFirst);
} catch (err) {
setState(() {
_loading = false;
diff --git a/pubspec.lock b/pubspec.lock
index bea221c..e012681 100644
--- a/pubspec.lock
+++ b/pubspec.lock
@@ -838,6 +838,27 @@ packages:
url: "https://pub.dartlang.org"
source: hosted
version: "1.3.0"
+ uni_links:
+ dependency: "direct main"
+ 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:
diff --git a/pubspec.yaml b/pubspec.yaml
index 7c30409..13199f7 100644
--- a/pubspec.yaml
+++ b/pubspec.yaml
@@ -1,6 +1,6 @@
name: spot
description: Find local video posts
-version: 1.4.2+64
+version: 1.4.3+65
publish_to: none
environment:
@@ -32,6 +32,7 @@ dependencies:
flutter_video_info: ^1.2.0
shared_preferences: ^2.0.6
google_maps_cluster_manager: ^3.0.0+1
+ uni_links: ^0.5.1
dev_dependencies:
flutter_test:
diff --git a/test/pages/tab_page_test.dart b/test/pages/tab_page_test.dart
index ca6d768..9b671c8 100644
--- a/test/pages/tab_page_test.dart
+++ b/test/pages/tab_page_test.dart
@@ -9,6 +9,7 @@ import 'package:mocktail/mocktail.dart';
import 'package:spot/components/frosted_dialog.dart';
import 'package:spot/components/notification_dot.dart';
import 'package:spot/cubits/notification/notification_cubit.dart';
+import 'package:spot/data_profiders/app_link_provider.dart';
import 'package:spot/models/notification.dart';
import 'package:spot/pages/edit_profile_page.dart';
import 'package:spot/pages/login_page.dart';
@@ -25,9 +26,21 @@ import '../test_resources/constants.dart';
class MockNavigatorObserver extends Mock implements NavigatorObserver {}
+class MockAppLinkProvider extends Mock implements AppLinkProvider {}
+
+class BuildContextFake extends Fake implements BuildContext {}
+
void main() {
+ late final AppLinkProvider _appLinkProvider;
+
/// This will allow http request to be sent within test code
- setUpAll(() => HttpOverrides.global = null);
+ setUpAll(() {
+ HttpOverrides.global = null;
+ _appLinkProvider = MockAppLinkProvider();
+ registerFallbackValue(BuildContextFake());
+ when(() => _appLinkProvider.setupAppLinks(any()))
+ .thenAnswer((_) async => null);
+ });
group('TabPage', () {
group('signed in', () {
late final Repository repository;
@@ -40,7 +53,7 @@ void main() {
.thenReturn(Completer()..complete());
});
testWidgets('Every tab gets rendered', (tester) async {
- final tabPage = TabPage();
+ final tabPage = TabPage(appLinkProvider: _appLinkProvider);
await tester.pumpApp(
widget: MultiBlocProvider(
providers: [
@@ -59,7 +72,7 @@ void main() {
});
testWidgets('Initial index is 0', (tester) async {
- final tabPage = TabPage();
+ final tabPage = TabPage(appLinkProvider: _appLinkProvider);
await tester.pumpApp(
widget: MultiBlocProvider(
providers: [
@@ -75,7 +88,7 @@ void main() {
});
testWidgets('Tapping Home goes to tab index 0', (tester) async {
- final tabPage = TabPage();
+ final tabPage = TabPage(appLinkProvider: _appLinkProvider);
await tester.pumpApp(
widget: MultiBlocProvider(
providers: [
@@ -92,7 +105,7 @@ void main() {
expect(tabPage.createState().currentIndex, 0);
});
testWidgets('Tapping Search goes to tab index 1', (tester) async {
- final tabPage = TabPage();
+ final tabPage = TabPage(appLinkProvider: _appLinkProvider);
await tester.pumpApp(
widget: MultiBlocProvider(
providers: [
@@ -111,7 +124,7 @@ void main() {
});
testWidgets('Tapping Notifications goes to tab index 2 when signed in',
(tester) async {
- final tabPage = TabPage();
+ final tabPage = TabPage(appLinkProvider: _appLinkProvider);
await tester.pumpApp(
widget: MultiBlocProvider(
providers: [
@@ -132,7 +145,7 @@ void main() {
});
testWidgets('Tapping Profile goes to tab index 3 when signed in',
(tester) async {
- final tabPage = TabPage();
+ final tabPage = TabPage(appLinkProvider: _appLinkProvider);
await tester.pumpApp(
widget: MultiBlocProvider(
providers: [
@@ -169,7 +182,7 @@ void main() {
create: (context) => NotificationCubit(
repository: RepositoryProvider.of(context))),
],
- child: TabPage(),
+ child: TabPage(appLinkProvider: _appLinkProvider),
),
repository: repository,
);
@@ -191,7 +204,7 @@ void main() {
create: (context) => NotificationCubit(
repository: RepositoryProvider.of(context))),
],
- child: TabPage(),
+ child: TabPage(appLinkProvider: _appLinkProvider),
),
repository: repository,
);
@@ -214,7 +227,7 @@ void main() {
create: (context) => NotificationCubit(
repository: RepositoryProvider.of(context))),
],
- child: TabPage(),
+ child: TabPage(appLinkProvider: _appLinkProvider),
),
repository: repository,
);
@@ -249,7 +262,7 @@ void main() {
create: (context) => NotificationCubit(
repository: RepositoryProvider.of(context))),
],
- child: TabPage(),
+ child: TabPage(appLinkProvider: _appLinkProvider),
),
repository: repository,
);
@@ -338,7 +351,7 @@ void main() {
.thenAnswer(
(invocation) async => 'something random @Tyler yay @Sam');
- final tabPage = TabPage();
+ final tabPage = TabPage(appLinkProvider: _appLinkProvider);
await tester.pumpApp(
widget: MultiBlocProvider(
providers: [
@@ -400,7 +413,7 @@ void main() {
when(() => repository.getVideosFromUid('aaa'))
.thenAnswer((_) => Future.value([]));
- final tabPage = TabPage();
+ final tabPage = TabPage(appLinkProvider: _appLinkProvider);
await tester.pumpApp(
widget: MultiBlocProvider(
providers: [
@@ -460,7 +473,7 @@ void main() {
when(() => repository.getVideosFromUid('aaa'))
.thenAnswer((_) => Future.value([]));
- final tabPage = TabPage();
+ final tabPage = TabPage(appLinkProvider: _appLinkProvider);
await tester.pumpApp(
widget: MultiBlocProvider(
providers: [
@@ -524,7 +537,7 @@ void main() {
when(() => repository.getVideosFromUid('aaa'))
.thenAnswer((_) => Future.value([]));
- final tabPage = TabPage();
+ final tabPage = TabPage(appLinkProvider: _appLinkProvider);
await tester.pumpApp(
widget: MultiBlocProvider(
providers: [
@@ -581,7 +594,7 @@ void main() {
when(() => repository.getVideosFromUid('aaa'))
.thenAnswer((_) => Future.value([]));
- final tabPage = TabPage();
+ final tabPage = TabPage(appLinkProvider: _appLinkProvider);
await tester.pumpApp(
widget: MultiBlocProvider(
providers: [