Browse Source

feat: implement portrait lock, pause media on navigation, update assets, and refactor AppDelegate for implicit engine support

develop
Ali Gopal Pour 1 week ago
parent
commit
860f29192c
  1. 1
      android/app/src/main/AndroidManifest.xml
  2. BIN
      assets/audios/home.mp3
  3. BIN
      assets/audios/home3.mp3
  4. BIN
      assets/audios/question.mp3
  5. BIN
      assets/audios/question3.mp3
  6. 2
      ios/Flutter/AppFrameworkInfo.plist
  7. 9
      ios/Podfile.lock
  8. 7
      ios/Runner/AppDelegate.swift
  9. 42
      ios/Runner/Info.plist
  10. 2
      lib/features/intro/presentation/bloc/intro_bloc.dart
  11. 325
      lib/features/language/presentation/ui/language_page.dart
  12. 2
      pubspec.yaml

1
android/app/src/main/AndroidManifest.xml

@ -10,6 +10,7 @@
android:exported="true"
android:launchMode="singleTop"
android:taskAffinity=""
android:screenOrientation="portrait"
android:theme="@style/LaunchTheme"
android:configChanges="orientation|keyboardHidden|keyboard|screenSize|smallestScreenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode"
android:hardwareAccelerated="true"

BIN
assets/audios/home.mp3

BIN
assets/audios/home3.mp3

BIN
assets/audios/question.mp3

BIN
assets/audios/question3.mp3

2
ios/Flutter/AppFrameworkInfo.plist

@ -20,7 +20,5 @@
<string>????</string>
<key>CFBundleVersion</key>
<string>1.0</string>
<key>MinimumOSVersion</key>
<string>13.0</string>
</dict>
</plist>

9
ios/Podfile.lock

@ -12,9 +12,6 @@ PODS:
- FlutterMacOS
- package_info_plus (0.4.5):
- Flutter
- path_provider_foundation (0.0.1):
- Flutter
- FlutterMacOS
- shared_preferences_foundation (0.0.1):
- Flutter
- FlutterMacOS
@ -34,7 +31,6 @@ DEPENDENCIES:
- flutter_archive (from `.symlinks/plugins/flutter_archive/ios`)
- just_audio (from `.symlinks/plugins/just_audio/darwin`)
- package_info_plus (from `.symlinks/plugins/package_info_plus/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`)
- video_player_avfoundation (from `.symlinks/plugins/video_player_avfoundation/darwin`)
@ -57,8 +53,6 @@ EXTERNAL SOURCES:
:path: ".symlinks/plugins/just_audio/darwin"
package_info_plus:
:path: ".symlinks/plugins/package_info_plus/ios"
path_provider_foundation:
:path: ".symlinks/plugins/path_provider_foundation/darwin"
shared_preferences_foundation:
:path: ".symlinks/plugins/shared_preferences_foundation/darwin"
url_launcher_ios:
@ -75,10 +69,9 @@ SPEC CHECKSUMS:
flutter_archive: ad8edfd7f7d1bb12058d05424ba93e27d9930efe
just_audio: 4e391f57b79cad2b0674030a00453ca5ce817eed
package_info_plus: af8e2ca6888548050f16fa2f1938db7b5a5df499
path_provider_foundation: bb55f6dbba17d0dccd6737fe6f7f34fbd0376880
shared_preferences_foundation: 7036424c3d8ec98dfe75ff1667cb0cd531ec82bb
url_launcher_ios: 7a95fa5b60cc718a708b8f2966718e93db0cef1b
video_player_avfoundation: dd410b52df6d2466a42d28550e33e4146928280a
video_player_avfoundation: 3453f792138786248960ca029747fcd9f318ef52
wakelock_plus: e29112ab3ef0b318e58cfa5c32326458be66b556
ZIPFoundation: b8c29ea7ae353b309bc810586181fd073cb3312c

7
ios/Runner/AppDelegate.swift

@ -2,12 +2,15 @@ import Flutter
import UIKit
@main
@objc class AppDelegate: FlutterAppDelegate {
@objc class AppDelegate: FlutterAppDelegate, FlutterImplicitEngineDelegate {
override func application(
_ application: UIApplication,
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
) -> Bool {
GeneratedPluginRegistrant.register(with: self)
return super.application(application, didFinishLaunchingWithOptions: launchOptions)
}
func didInitializeImplicitFlutterEngine(_ engineBridge: FlutterImplicitEngineBridge) {
GeneratedPluginRegistrant.register(with: engineBridge.pluginRegistry)
}
}

42
ios/Runner/Info.plist

@ -2,11 +2,8 @@
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>NSAppTransportSecurity</key>
<dict>
<key>NSAllowsArbitraryLoads</key>
<key>CADisableMinimumFrameDurationOnPhone</key>
<true/>
</dict>
<key>CFBundleDevelopmentRegion</key>
<string>$(DEVELOPMENT_LANGUAGE)</string>
<key>CFBundleDisplayName</key>
@ -29,6 +26,34 @@
<string>$(FLUTTER_BUILD_NUMBER)</string>
<key>LSRequiresIPhoneOS</key>
<true/>
<key>NSAppTransportSecurity</key>
<dict>
<key>NSAllowsArbitraryLoads</key>
<true/>
</dict>
<key>UIApplicationSceneManifest</key>
<dict>
<key>UIApplicationSupportsMultipleScenes</key>
<false/>
<key>UISceneConfigurations</key>
<dict>
<key>UIWindowSceneSessionRoleApplication</key>
<array>
<dict>
<key>UISceneClassName</key>
<string>UIWindowScene</string>
<key>UISceneConfigurationName</key>
<string>flutter</string>
<key>UISceneDelegateClassName</key>
<string>FlutterSceneDelegate</string>
<key>UISceneStoryboardFile</key>
<string>Main</string>
</dict>
</array>
</dict>
</dict>
<key>UIApplicationSupportsIndirectInputEvents</key>
<true/>
<key>UILaunchStoryboardName</key>
<string>LaunchScreen</string>
<key>UIMainStoryboardFile</key>
@ -36,19 +61,10 @@
<key>UISupportedInterfaceOrientations</key>
<array>
<string>UIInterfaceOrientationPortrait</string>
<string>UIInterfaceOrientationLandscapeLeft</string>
<string>UIInterfaceOrientationLandscapeRight</string>
</array>
<key>UISupportedInterfaceOrientations~ipad</key>
<array>
<string>UIInterfaceOrientationPortrait</string>
<string>UIInterfaceOrientationPortraitUpsideDown</string>
<string>UIInterfaceOrientationLandscapeLeft</string>
<string>UIInterfaceOrientationLandscapeRight</string>
</array>
<key>CADisableMinimumFrameDurationOnPhone</key>
<true/>
<key>UIApplicationSupportsIndirectInputEvents</key>
<true/>
</dict>
</plist>

2
lib/features/intro/presentation/bloc/intro_bloc.dart

@ -73,6 +73,7 @@ class IntroBloc extends Bloc<IntroEvent, IntroState> {
/// ------------Functions------------
Future<void> goToHomePage() async {
currentPodController.pause();
await LocalStorage.saveData(key: MyConstants.firstIntro, value: 'true');
if (MyContext.get.mounted) {
MyContext.get.goNamed(Routes.homePage);
@ -148,6 +149,7 @@ class IntroBloc extends Bloc<IntroEvent, IntroState> {
Emitter emit,
) async {
_shouldResumeCurrentVideo = false;
currentPodController.pause();
switch (state.currentIntro) {
case 0:
podController2.play();

325
lib/features/language/presentation/ui/language_page.dart

@ -1,325 +0,0 @@
import 'package:auto_size_text/auto_size_text.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:go_router/go_router.dart';
import 'package:hadi_hoda_flutter/common_ui/resources/my_assets.dart';
import 'package:hadi_hoda_flutter/common_ui/resources/my_spaces.dart';
import 'package:hadi_hoda_flutter/common_ui/resources/my_text_style.dart';
import 'package:hadi_hoda_flutter/core/constants/my_constants.dart';
import 'package:hadi_hoda_flutter/core/routers/my_routes.dart';
import 'package:hadi_hoda_flutter/core/status/base_status.dart';
import 'package:hadi_hoda_flutter/core/utils/my_localization.dart';
import 'package:hadi_hoda_flutter/core/utils/screen_size.dart';
import 'package:hadi_hoda_flutter/core/utils/set_platform_size.dart';
import 'package:hadi_hoda_flutter/core/widgets/button/my_blue_button.dart';
import 'package:hadi_hoda_flutter/core/widgets/error/error_state.dart';
import 'package:hadi_hoda_flutter/core/widgets/images/my_image.dart';
import 'package:hadi_hoda_flutter/features/app/presentation/bloc/app_bloc.dart';
import 'package:hadi_hoda_flutter/features/app/presentation/bloc/app_event.dart';
import 'package:hadi_hoda_flutter/features/download/domain/entities/download_entity.dart';
import 'package:hadi_hoda_flutter/features/download/presentation/bloc/download_bloc.dart';
import 'package:hadi_hoda_flutter/features/download/presentation/bloc/download_event.dart';
import 'package:hadi_hoda_flutter/features/language/presentation/bloc/language_bloc.dart';
import 'package:hadi_hoda_flutter/features/language/presentation/bloc/language_event.dart';
import 'package:hadi_hoda_flutter/features/language/presentation/bloc/language_state.dart';
import 'package:wheel_chooser/wheel_chooser.dart';
import '../../../../core/widgets/animations/rotation_anim.dart';
import '../../domain/entity/language_entity.dart';
class LanguagePage extends StatefulWidget {
const LanguagePage({super.key});
@override
State<LanguagePage> createState() => _LanguagePageState();
}
class _LanguagePageState extends State<LanguagePage> {
final controller = FixedExtentScrollController(initialItem: 50);
bool _isEnabled = false;
LanguageEntity? _selectedLanguage;
@override
void initState() {
super.initState();
_selectedLanguage =
context.read<LanguageBloc>().state.selectedLanguage ??
const LanguageEntity(
code: 'en',
title: '',
displayName: 'English (English',
);
context.read<LanguageBloc>().add(const GetLanguagesEvent());
}
@override
void dispose() {
controller.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return Scaffold(
body: Container(
height: context.heightScreen,
width: context.widthScreen,
decoration: BoxDecoration(
gradient: const LinearGradient(
begin: Alignment.topCenter,
end: Alignment.bottomCenter,
colors: [Color(0XFF00154C), Color(0XFF150532)],
),
image: DecorationImage(
image: const AssetImage(MyAssets.pattern),
scale: 3,
repeat: ImageRepeat.repeat,
colorFilter: ColorFilter.mode(
Colors.white.withValues(alpha: 0.2),
BlendMode.srcIn,
),
),
),
child: BlocConsumer<LanguageBloc, LanguageState>(
listener: (context, state) async {
if (state.languages.isNotEmpty && !_isEnabled) {
int targetIndex = 0;
if (state.selectedLanguage != null) {
targetIndex = state.languages.indexWhere(
(l) => l.code == state.selectedLanguage!.code,
);
if (targetIndex == -1) targetIndex = 0;
}
await Future.delayed(const Duration(milliseconds: 50));
if (controller.hasClients) {
await controller.animateToItem(
targetIndex,
duration: const Duration(seconds: 1),
curve: Curves.easeOutCirc,
);
if (state.selectedLanguage == null && context.mounted) {
context.read<LanguageBloc>().add(
SelectLanguageEvent(state.languages[targetIndex]),
);
}
}
setState(() => _isEnabled = true);
}
},
builder: (context, state) {
if (state.getLanguagesStatus is BaseLoading) {
return Center(child: _loading(context));
}
if (state.getLanguagesStatus is BaseError) {
return Padding(
padding: EdgeInsets.symmetric(
vertical:
MediaQuery.viewPaddingOf(context).bottom + MySpaces.s16,
horizontal: 60,
),
child: ErrorState(
onTap: () {
context.read<LanguageBloc>().add(const GetLanguagesEvent());
},
),
);
}
final double itemSize =
setSize(context: context, mobile: 45, tablet: 60) ?? 45;
final selectStyle = MYTextStyle.titr1.copyWith(
// Keep same size as before (WheelChooser.choices)
fontSize: itemSize * 0.34,
color: Colors.white,
);
final unSelectStyle = MYTextStyle.titr1.copyWith(
fontSize: itemSize * 0.34,
color: Colors.white.withValues(alpha: 0.5),
);
return Padding(
padding: EdgeInsets.only(
left: setSize(context: context, mobile: 50, tablet: 0.3.w) ?? 0,
right:
setSize(context: context, mobile: 50, tablet: 0.3.w) ?? 0,
bottom: MySpaces.s40 + MediaQuery.paddingOf(context).bottom,
top: 60,
),
child: Column(
children: [
_title(context),
const SizedBox(height: 72),
Expanded(
child: ShaderMask(
shaderCallback: (rect) {
return const LinearGradient(
begin: Alignment.topCenter,
end: Alignment.bottomCenter,
colors: [
Color(0x00FFF7FA),
Color(0x1AFFF7FA),
Color(0xFFFFFFFF),
Color(0x1AFEFCFD),
Color(0x00FEFCFD),
],
).createShader(rect);
},
child: Stack(
children: [
Center(
child: Container(
width: double.infinity,
alignment: Alignment.centerLeft,
height: itemSize,
padding: const EdgeInsets.symmetric(
horizontal: 18,
),
decoration: BoxDecoration(
color: Colors.white.withValues(alpha: .2),
borderRadius: BorderRadius.circular(12),
),
child: Transform.translate(
offset:
const Offset(-4, 0),
child: const MyImage(image: MyAssets.check),
),
),
),
WheelChooser.custom(
controller: controller,
datas: null,
startPosition: null,
perspective: 0.00000001,
listWidth: double.infinity,
listHeight: double.infinity,
itemSize: itemSize,
isInfinite: true,
onValueChanged: (position) {
if (state.languages.isEmpty) return;
final pos = position is int
? position
: int.tryParse(position.toString()) ?? 0;
final index =
((pos % state.languages.length) +
state.languages.length) %
state.languages.length;
final lang = state.languages[index];
setState(() {
_selectedLanguage = lang;
});
},
children: state.languages.map((lang) {
final isSelected =
_selectedLanguage?.code == lang.code;
return Center(
child: Padding(
padding: const EdgeInsets.symmetric(
horizontal: 36,
),
child: MediaQuery(
data: MediaQuery.of(context).copyWith(
textScaler: const TextScaler.linear(1.5),
),
child: AutoSizeText(
lang.displayName,
textAlign: TextAlign.center,
maxLines: 2,
minFontSize: 10,
stepGranularity: 0.5,
overflow: TextOverflow.ellipsis,
style: isSelected
? selectStyle
: unSelectStyle,
),
),
),
);
}).toList(),
),
],
),
),
),
const SizedBox(height: 72),
_btn(context, state),
],
),
);
},
),
),
);
}
Widget _loading(BuildContext context) {
return RotationAnim(
child: MyImage(
image: MyAssets.loading,
size: setSize(context: context, mobile: 110, tablet: 145),
),
);
}
Widget _title(BuildContext context) {
return Row(
spacing: MySpaces.s10,
mainAxisAlignment: MainAxisAlignment.center,
children: [
const MyImage(image: MyAssets.lang, size: 28),
AutoSizeText(
context.translate.select_language,
minFontSize: 12,
maxFontSize: 20,
maxLines: 1,
textAlign: TextAlign.center,
style: MYTextStyle.titr0.copyWith(color: const Color(0XFF847AC4)),
),
],
);
}
Widget _btn(BuildContext context, LanguageState state) {
return MyBlueButton(
onTap: (_isEnabled && _selectedLanguage != null)
? () async {
context.read<LanguageBloc>().add(
SelectLanguageEvent(_selectedLanguage!),
);
final downloadBloc = context.read<DownloadBloc>();
// 1. Cancel any previous downloads.
downloadBloc.add(CancelDownloadEvent());
// 2. Update App Locale
context.read<AppBloc>().add(
ChangeLocaleEvent(_selectedLanguage!.locale),
);
await Future.delayed(const Duration(milliseconds: 80));
final lastDownloadedLevel = await downloadBloc
.lastDownloadedLevel();
if (!context.mounted) return;
if (lastDownloadedLevel >= MyConstants.firstDownloadBatchCount) {
context.goNamed(Routes.homePage);
} else {
context.read<DownloadBloc>().add(
const StartDownloadEvent(
toLevel: MyConstants.firstDownloadBatchCount,
),
);
context.goNamed(
Routes.downloadPage,
extra: const DownloadPageConfig(
downloadToLevel: MyConstants.firstDownloadBatchCount,
redirectTo: Routes.homePage,
),
);
}
}
: null,
title: context.translate.select,
);
}
}

2
pubspec.yaml

@ -1,7 +1,7 @@
name: hadi_hoda_flutter
description: "A new Flutter project."
publish_to: 'none'
version: 1.1.4+114
version: 1.1.6+116
environment:
sdk: ^3.9.2

Loading…
Cancel
Save