deep linking
This commit is contained in:
parent
3827209ade
commit
047de45e3b
|
|
@ -17,7 +17,6 @@
|
|||
android:icon="@mipmap/ic_launcher"
|
||||
android:label="Didvan"
|
||||
android:requestLegacyExternalStorage="true"
|
||||
|
||||
android:usesCleartextTraffic="true">
|
||||
<receiver
|
||||
android:name=".FavWidget"
|
||||
|
|
@ -50,8 +49,6 @@
|
|||
android:resource="@xml/provider_paths" />
|
||||
</provider>
|
||||
|
||||
<!-- Begin FlutterDownloader customization -->
|
||||
<!-- disable default Initializer -->
|
||||
<provider
|
||||
android:name="androidx.startup.InitializationProvider"
|
||||
android:authorities="${applicationId}.androidx-startup"
|
||||
|
|
@ -63,18 +60,14 @@
|
|||
tools:node="remove" />
|
||||
</provider>
|
||||
|
||||
<!-- declare customized Initializer -->
|
||||
<provider
|
||||
android:name="vn.hunghd.flutterdownloader.FlutterDownloaderInitializer"
|
||||
android:authorities="${applicationId}.flutter-downloader-init"
|
||||
android:exported="false">
|
||||
<!-- changes this number to configure the maximum number of concurrent tasks -->
|
||||
<meta-data
|
||||
android:name="vn.hunghd.flutterdownloader.MAX_CONCURRENT_TASKS"
|
||||
android:value="5" />
|
||||
</provider>
|
||||
<!-- End FlutterDownloader customization -->
|
||||
|
||||
<activity android:name=".WebActivity" />
|
||||
<activity
|
||||
android:name=".FullscreenWebViewActivity"
|
||||
|
|
@ -92,17 +85,6 @@
|
|||
android:exported="true"
|
||||
android:windowSoftInputMode="adjustResize">
|
||||
|
||||
<intent-filter>
|
||||
<action android:name="FLUTTER_NOTIFICATION_CLICK" />
|
||||
<category android:name="android.intent.category.DEFAULT" />
|
||||
</intent-filter>
|
||||
|
||||
<!--
|
||||
Specifies an Android theme to apply to this Activity as soon as
|
||||
the Android process has started. This theme is visible to the user
|
||||
while the Flutter UI initializes. After that, this theme continues
|
||||
to determine the Window background behind the Flutter UI.
|
||||
-->
|
||||
<meta-data
|
||||
android:name="flutterEmbedding"
|
||||
android:value="2" />
|
||||
|
|
@ -110,18 +92,22 @@
|
|||
android:name="io.flutter.embedding.android.NormalTheme"
|
||||
android:resource="@style/NormalTheme" />
|
||||
|
||||
<!--
|
||||
Displays an Android View that continues showing the launch screen
|
||||
Drawable until Flutter paints its first frame, then this splash
|
||||
screen fades out. A splash screen is useful to avoid any visual
|
||||
gap between the end of Android's launch screen and the painting of
|
||||
Flutter's first frame.
|
||||
-->
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.MAIN" />
|
||||
|
||||
<category android:name="android.intent.category.LAUNCHER" />
|
||||
</intent-filter>
|
||||
|
||||
<intent-filter>
|
||||
<action android:name="FLUTTER_NOTIFICATION_CLICK" />
|
||||
<category android:name="android.intent.category.DEFAULT" />
|
||||
</intent-filter>
|
||||
|
||||
<intent-filter android:autoVerify="true">
|
||||
<action android:name="android.intent.action.VIEW" />
|
||||
<category android:name="android.intent.category.DEFAULT" />
|
||||
<category android:name="android.intent.category.BROWSABLE" />
|
||||
<data android:scheme="https" android:host="web.didvan.com" />
|
||||
</intent-filter>
|
||||
</activity>
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ import 'dart:async';
|
|||
import 'dart:io';
|
||||
|
||||
import 'package:android_intent_plus/android_intent.dart';
|
||||
import 'package:app_links/app_links.dart';
|
||||
import 'package:bot_toast/bot_toast.dart';
|
||||
import 'package:didvan/config/theme_data.dart';
|
||||
import 'package:didvan/firebase_options.dart';
|
||||
|
|
@ -95,6 +96,8 @@ class Didvan extends StatefulWidget {
|
|||
}
|
||||
|
||||
class _DidvanState extends State<Didvan> with WidgetsBindingObserver {
|
||||
late AppLinks _appLinks;
|
||||
StreamSubscription<Uri>? _linkSubscription;
|
||||
@override
|
||||
void didChangeDependencies() {
|
||||
super.didChangeDependencies();
|
||||
|
|
@ -104,17 +107,46 @@ class _DidvanState extends State<Didvan> with WidgetsBindingObserver {
|
|||
void initState() {
|
||||
WidgetsBinding.instance.addObserver(this);
|
||||
super.initState();
|
||||
_initDeepLinks();
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
WidgetsBinding.instance.removeObserver(this);
|
||||
_linkSubscription?.cancel();
|
||||
if (MediaService.currentPodcast != null) {
|
||||
MediaService.audioPlayer.dispose();
|
||||
}
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
Future<void> _initDeepLinks() async {
|
||||
_appLinks = AppLinks();
|
||||
|
||||
// بررسی لینک اولیه در زمان باز شدن اپلیکیشن
|
||||
final initialUri = await _appLinks.getInitialLink();
|
||||
if (initialUri != null) {
|
||||
_navigateTo(initialUri);
|
||||
}
|
||||
|
||||
// گوش دادن به لینکهای جدید زمانی که اپلیکیشن در حال اجراست
|
||||
_linkSubscription = _appLinks.uriLinkStream.listen((uri) {
|
||||
_navigateTo(uri);
|
||||
});
|
||||
}
|
||||
|
||||
/// تابع کمکی برای ناوبری
|
||||
void _navigateTo(Uri uri) {
|
||||
if (mounted) {
|
||||
String path = uri.path;
|
||||
if (uri.fragment.isNotEmpty) {
|
||||
path = "/${uri.fragment}";
|
||||
}
|
||||
navigatorKey.currentState?.pushNamed(path);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
bool b = true;
|
||||
|
||||
@override
|
||||
|
|
|
|||
|
|
@ -1,6 +1,8 @@
|
|||
// ignore_for_file: avoid_print
|
||||
|
||||
import 'package:didvan/models/ai/ai_chat_args.dart';
|
||||
import 'package:didvan/models/requests/news.dart';
|
||||
import 'package:didvan/models/requests/radar.dart';
|
||||
import 'package:didvan/models/story_model.dart';
|
||||
import 'package:didvan/views/ai/ai_chat_page.dart';
|
||||
import 'package:didvan/views/ai/ai_chat_state.dart';
|
||||
|
|
@ -74,6 +76,35 @@ import '../views/notification_time/notification_time.dart';
|
|||
|
||||
class RouteGenerator {
|
||||
static Route<dynamic> generateRoute(RouteSettings settings) {
|
||||
final uri = Uri.parse(settings.name ?? '');
|
||||
if (uri.pathSegments.isNotEmpty) {
|
||||
if (uri.pathSegments.first == 'news' && uri.pathSegments.length > 1) {
|
||||
final id = int.tryParse(uri.pathSegments[1]);
|
||||
if (id != null) {
|
||||
return _createRoute(
|
||||
ChangeNotifierProvider<NewsDetailsState>(
|
||||
create: (context) => NewsDetailsState(),
|
||||
child: NewsDetails(
|
||||
pageData: {'id': id, 'args': const NewsRequestArgs(page: 0)},
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
if (uri.pathSegments.first == 'radar' && uri.pathSegments.length > 1) {
|
||||
final id = int.tryParse(uri.pathSegments[1]);
|
||||
if (id != null) {
|
||||
return _createRoute(
|
||||
ChangeNotifierProvider<RadarDetailsState>(
|
||||
create: (context) => RadarDetailsState(),
|
||||
child: RadarDetails(
|
||||
pageData: {'id': id, 'args': const RadarRequestArgs(page: 0)},
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!kIsWeb) {
|
||||
HomeWidget.saveWidgetData("cRoute", settings.name!);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -61,6 +61,7 @@ class RequestService {
|
|||
_requestBody == null ? null : jsonEncode(_requestBody);
|
||||
|
||||
Future<void> httpGet() async {
|
||||
log('req is: $url', name: 'RequestService');
|
||||
try {
|
||||
final response = await http
|
||||
.get(
|
||||
|
|
|
|||
|
|
@ -49,6 +49,11 @@ class _AuthenticationState extends State<Authentication> {
|
|||
if (state.currentPageIndex == 0) {
|
||||
return true;
|
||||
}
|
||||
// Check if on OTP screen and no password exists
|
||||
if (state.currentPageIndex == 2 && !state.hasPassword) {
|
||||
state.currentPageIndex = 0; // Go back to username screen
|
||||
return false;
|
||||
}
|
||||
state.currentPageIndex--;
|
||||
return false;
|
||||
},
|
||||
|
|
|
|||
|
|
@ -15,6 +15,7 @@ class AuthenticationState extends CoreProvier {
|
|||
String password = '';
|
||||
String _verificationCode = '';
|
||||
bool isShowCustomDialog = true;
|
||||
bool hasPassword = true;
|
||||
|
||||
set currentPageIndex(int value) {
|
||||
_currentPageIndex = value;
|
||||
|
|
@ -36,6 +37,7 @@ class AuthenticationState extends CoreProvier {
|
|||
appState = AppState.idle;
|
||||
|
||||
final bool hasPassword = service.result['hasPassword'];
|
||||
this.hasPassword = hasPassword;
|
||||
|
||||
if (hasPassword) {
|
||||
currentPageIndex = 1;
|
||||
|
|
|
|||
|
|
@ -22,6 +22,7 @@ class _ResetPasswordState extends State<ResetPassword> {
|
|||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final authState = context.watch<AuthenticationState>();
|
||||
return Form(
|
||||
key: _formKey,
|
||||
child: AuthenticationLayout(
|
||||
|
|
@ -74,7 +75,7 @@ class _ResetPasswordState extends State<ResetPassword> {
|
|||
}
|
||||
}
|
||||
},
|
||||
title: 'تغییر رمز عبور',
|
||||
title: authState.hasPassword ? 'تغییر رمز عبور' : 'تایید رمز عبور',
|
||||
),
|
||||
const SizedBox(
|
||||
height: 48,
|
||||
|
|
|
|||
|
|
@ -1,3 +1,5 @@
|
|||
// lib/views/authentication/widgets/authentication_app_bar.dart
|
||||
|
||||
import 'package:didvan/config/theme_data.dart';
|
||||
import 'package:didvan/constants/app_icons.dart';
|
||||
import 'package:didvan/views/authentication/authentication_state.dart';
|
||||
|
|
@ -18,12 +20,24 @@ class AuthenticationAppBar extends StatelessWidget {
|
|||
DidvanIconButton(
|
||||
icon: DidvanIcons.back_regular,
|
||||
onPressed: () {
|
||||
// اگر در صفحه اول باشیم، از صفحه احراز هویت خارج میشویم
|
||||
if (state.currentPageIndex == 0) {
|
||||
Navigator.of(context).pop();
|
||||
return;
|
||||
}
|
||||
|
||||
// ** منطق جدید برای بازگشت از صفحه OTP **
|
||||
// اگر کاربر رمز عبور نداشته و در صفحه OTP باشد
|
||||
if (state.currentPageIndex == 2 && !state.hasPassword) {
|
||||
// او را به صفحه اول (ورود شماره) برمیگردانیم
|
||||
state.currentPageIndex = 0;
|
||||
return;
|
||||
}
|
||||
|
||||
// در غیر این صورت، به صفحه قبلی برمیگردیم
|
||||
state.currentPageIndex--;
|
||||
}),
|
||||
},
|
||||
),
|
||||
const SizedBox(
|
||||
width: 4,
|
||||
),
|
||||
|
|
|
|||
|
|
@ -43,14 +43,13 @@ class MainPageState extends CoreProvier {
|
|||
try {
|
||||
swotItems = await SwotService.fetchSwotItems();
|
||||
} catch (e) {
|
||||
// در صورت بروز خطا، میتوانید اینجا آن را مدیریت کنید
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _fetchStories() async {
|
||||
try {
|
||||
stories = await StoryService.getStories();
|
||||
// ignore: avoid_print
|
||||
print("Fetched ${stories.length} stories.");
|
||||
} catch (e) {
|
||||
stories = [];
|
||||
|
|
|
|||
|
|
@ -23,14 +23,11 @@ class StorySection extends StatelessWidget {
|
|||
child: _StoryCircle(
|
||||
userStories: userStories,
|
||||
onTap: () {
|
||||
bool allStoriesViewed = stories.every((userStories) =>
|
||||
userStories.stories.every((story) => story.isViewed.value));
|
||||
|
||||
Navigator.of(context).pushNamed(
|
||||
Routes.storyViewer,
|
||||
arguments: {
|
||||
'stories': stories,
|
||||
'tappedIndex': allStoriesViewed ? 0 : index,
|
||||
'tappedIndex': index,
|
||||
},
|
||||
);
|
||||
},
|
||||
|
|
@ -50,9 +47,11 @@ class _StoryCircle extends StatelessWidget {
|
|||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
// ValueNotifier برای پیگیری وضعیت مشاهده همه استوریها
|
||||
final allStoriesViewed = ValueNotifier<bool>(
|
||||
userStories.stories.every((story) => story.isViewed.value));
|
||||
|
||||
// افزودن Listener به هر استوری
|
||||
for (var story in userStories.stories) {
|
||||
story.isViewed.addListener(() {
|
||||
allStoriesViewed.value =
|
||||
|
|
@ -65,6 +64,7 @@ class _StoryCircle extends StatelessWidget {
|
|||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
// استفاده از ValueListenableBuilder برای تغییر رنگ حاشیه
|
||||
ValueListenableBuilder<bool>(
|
||||
valueListenable: allStoriesViewed,
|
||||
builder: (context, isViewed, child) {
|
||||
|
|
@ -104,7 +104,7 @@ class _StoryCircle extends StatelessWidget {
|
|||
child: ClipOval(
|
||||
child: Image.asset(
|
||||
userStories.user
|
||||
.profileImageUrl,
|
||||
.profileImageUrl, // Assuming this is a local asset
|
||||
fit: BoxFit.cover,
|
||||
width: 50.0,
|
||||
height: 50.0,
|
||||
|
|
|
|||
|
|
@ -92,14 +92,14 @@ class _UserStoryViewerState extends State<UserStoryViewer>
|
|||
super.initState();
|
||||
_animationController = AnimationController(vsync: this);
|
||||
|
||||
final allStoriesInGroupViewed =
|
||||
widget.userStories.stories.every((story) => story.isViewed.value);
|
||||
// final allStoriesInGroupViewed =
|
||||
// widget.userStories.stories.every((story) => story.isViewed.value);
|
||||
|
||||
if (allStoriesInGroupViewed) {
|
||||
for (final story in widget.userStories.stories) {
|
||||
story.isViewed.value = false;
|
||||
}
|
||||
}
|
||||
// if (allStoriesInGroupViewed) {
|
||||
// for (final story in widget.userStories.stories) {
|
||||
// story.isViewed.value = false;
|
||||
// }
|
||||
// }
|
||||
|
||||
_currentStoryIndex =
|
||||
widget.userStories.stories.indexWhere((story) => !story.isViewed.value);
|
||||
|
|
@ -244,12 +244,12 @@ class _UserStoryViewerState extends State<UserStoryViewer>
|
|||
return CachedNetworkImage(
|
||||
placeholder: (context, url) => const ShimmerPlaceholder(),
|
||||
imageUrl: story.url,
|
||||
fit: BoxFit.cover,
|
||||
fit: BoxFit.fill,
|
||||
width: double.infinity,
|
||||
height: double.infinity);
|
||||
case MediaType.gif:
|
||||
return Image.network(story.url,
|
||||
fit: BoxFit.cover, width: double.infinity, height: double.infinity);
|
||||
fit: BoxFit.fill, width: double.infinity, height: double.infinity);
|
||||
case MediaType.video:
|
||||
if (_videoController?.value.isInitialized ?? false) {
|
||||
return FittedBox(
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ import 'package:didvan/constants/app_icons.dart';
|
|||
import 'package:didvan/utils/extension.dart';
|
||||
import 'package:didvan/views/widgets/animated_visibility.dart';
|
||||
import 'package:didvan/views/widgets/didvan/text.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
|
||||
|
|
@ -108,7 +109,8 @@ class _DidvanTextFieldState extends State<DidvanTextField> {
|
|||
? TextDirection.ltr
|
||||
: TextDirection.rtl,
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.fromLTRB(8,8,0,15),
|
||||
padding: const EdgeInsets.fromLTRB(kIsWeb?8:8,kIsWeb?4:8,kIsWeb?8:0,kIsWeb?0:8),
|
||||
child: Center(
|
||||
child: TextFormField(
|
||||
inputFormatters: <TextInputFormatter>[
|
||||
if (!widget.acceptSpace)
|
||||
|
|
@ -154,6 +156,7 @@ class _DidvanTextFieldState extends State<DidvanTextField> {
|
|||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}),
|
||||
),
|
||||
|
|
|
|||
40
pubspec.lock
40
pubspec.lock
|
|
@ -33,6 +33,38 @@ packages:
|
|||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.8.4"
|
||||
app_links:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: app_links
|
||||
sha256: "85ed8fc1d25a76475914fff28cc994653bd900bc2c26e4b57a49e097febb54ba"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "6.4.0"
|
||||
app_links_linux:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: app_links_linux
|
||||
sha256: f5f7173a78609f3dfd4c2ff2c95bd559ab43c80a87dc6a095921d96c05688c81
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.0.3"
|
||||
app_links_platform_interface:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: app_links_platform_interface
|
||||
sha256: "05f5379577c513b534a29ddea68176a4d4802c46180ee8e2e966257158772a3f"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.0.2"
|
||||
app_links_web:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: app_links_web
|
||||
sha256: af060ed76183f9e2b87510a9480e56a5352b6c249778d07bd2c95fc35632a555
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.0.4"
|
||||
args:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
|
@ -669,6 +701,14 @@ packages:
|
|||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.3.2"
|
||||
gtk:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: gtk
|
||||
sha256: e8ce9ca4b1df106e4d72dad201d345ea1a036cc12c360f1a7d5a758f78ffa42c
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.1.0"
|
||||
highlight:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
|
|
|||
|
|
@ -15,7 +15,7 @@ publish_to: "none" # Remove this line if you wish to publish to pub.dev
|
|||
# In iOS, build-name is used as CFBundleShortVersionString while build-number used as CFBundleVersion.
|
||||
# Read more about iOS versioning at
|
||||
# https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html
|
||||
version: 4.0.1+4380
|
||||
version: 4.0.3+5000
|
||||
|
||||
environment:
|
||||
sdk: ">=2.19.0 <3.0.0"
|
||||
|
|
@ -113,6 +113,7 @@ dependencies:
|
|||
sms_autofill: ^2.4.1
|
||||
shimmer: ^3.0.0
|
||||
device_info_plus: ^11.5.0
|
||||
app_links: ^6.4.0
|
||||
# image_gallery_saver: ^2.0.3
|
||||
# fading_edge_scrollview: ^4.1.1
|
||||
dev_dependencies:
|
||||
|
|
|
|||
Loading…
Reference in New Issue