32 changed files with 672 additions and 157 deletions
-
2lib/core/auth_storage/auth_storage.dart
-
3lib/core/constants/my_api.dart
-
6lib/core/constants/my_constants.dart
-
13lib/core/routers/my_routes.dart
-
4lib/core/utils/local_storage.dart
-
116lib/features/download/data/datasource/download_datasource.dart
-
50lib/features/download/data/repository_impl/download_repository_impl.dart
-
0lib/features/download/domain/entities/download_entity.dart
-
10lib/features/download/domain/repository/download_repository.dart
-
16lib/features/download/domain/usecases/get_audios_usecase.dart
-
12lib/features/download/domain/usecases/loading_stream_audio_usecase.dart
-
4lib/features/download/domain/usecases/save_levels_usecase.dart
-
74lib/features/download/presentation/bloc/download_bloc.dart
-
5lib/features/download/presentation/bloc/download_event.dart
-
15lib/features/download/presentation/bloc/download_state.dart
-
130lib/features/download/presentation/ui/download_page.dart
-
136lib/features/download/presentation/ui/widgets/download_loading_widget.dart
-
60lib/features/intro/data/datasource/intro_datasource.dart
-
22lib/features/intro/data/repository_impl/intro_repository_impl.dart
-
5lib/features/intro/domain/repository/intro_repository.dart
-
6lib/features/intro/domain/usecases/get_images_usecase.dart
-
2lib/features/intro/domain/usecases/loading_stream_usecase.dart
-
52lib/features/intro/presentation/bloc/intro_bloc.dart
-
3lib/features/intro/presentation/bloc/intro_event.dart
-
6lib/features/intro/presentation/ui/intro_page.dart
-
2lib/features/intro/presentation/ui/widgets/intro_loading_widget.dart
-
27lib/features/language/presentation/bloc/language_bloc.dart
-
2lib/features/level/data/datasource/level_datasource.dart
-
11lib/features/level/presentation/bloc/level_bloc.dart
-
2lib/features/question/data/datasource/question_datasource.dart
-
21lib/init_bindings.dart
-
12lib/main.dart
@ -0,0 +1,116 @@ |
|||||
|
import 'dart:async'; |
||||
|
import 'dart:io'; |
||||
|
|
||||
|
import 'package:flutter_archive/flutter_archive.dart'; |
||||
|
import 'package:hadi_hoda_flutter/core/constants/my_api.dart'; |
||||
|
import 'package:hadi_hoda_flutter/core/constants/my_constants.dart'; |
||||
|
import 'package:hadi_hoda_flutter/core/error_handler/my_exception.dart'; |
||||
|
import 'package:hadi_hoda_flutter/core/network/http_request.dart'; |
||||
|
import 'package:hadi_hoda_flutter/core/response/base_response.dart'; |
||||
|
import 'package:hadi_hoda_flutter/core/utils/local_storage.dart'; |
||||
|
import 'package:hadi_hoda_flutter/core/utils/storage_path.dart'; |
||||
|
import 'package:hadi_hoda_flutter/features/download/domain/entities/download_entity.dart'; |
||||
|
import 'package:hadi_hoda_flutter/features/level/data/model/level_model.dart'; |
||||
|
import 'package:hadi_hoda_flutter/features/level/domain/entity/level_entity.dart'; |
||||
|
import 'package:hadi_hoda_flutter/features/level/domain/entity/total_data_entity.dart'; |
||||
|
import 'package:hive/hive.dart'; |
||||
|
|
||||
|
abstract class IDownloadDatasource { |
||||
|
Future<void> getAudios(); |
||||
|
Future<void> saveLevels(); |
||||
|
Stream<DownloadEntity> loadingStream(); |
||||
|
} |
||||
|
|
||||
|
class DownloadDatasourceImpl implements IDownloadDatasource { |
||||
|
final IHttpRequest httpRequest; |
||||
|
final StreamController<DownloadEntity> streamController = StreamController<DownloadEntity>.broadcast(); |
||||
|
|
||||
|
DownloadDatasourceImpl(this.httpRequest); |
||||
|
|
||||
|
@override |
||||
|
Future<void> getAudios() async { |
||||
|
final String filePath = '${StoragePath.documentDir.path}/audios.zip'; |
||||
|
final String selectedLanguage = |
||||
|
LocalStorage.readData(key: MyConstants.selectLanguage) ?? 'fa'; |
||||
|
|
||||
|
if(LocalStorage.readData(key: MyConstants.downloadedAudio) != 'true'){ |
||||
|
await httpRequest.download( |
||||
|
urlPath: MyApi.audios, |
||||
|
savePath: filePath, |
||||
|
queryParameters: { |
||||
|
'lang': selectedLanguage, |
||||
|
}, |
||||
|
onReceive: (count, total) { |
||||
|
streamController.add(DownloadEntity( |
||||
|
count: count / 1, |
||||
|
total: total / 1, |
||||
|
percent: (count / total) * 100, |
||||
|
)); |
||||
|
}, |
||||
|
).then((value) async { |
||||
|
await LocalStorage.saveData( |
||||
|
key: MyConstants.downloadedAudio, |
||||
|
value: 'true', |
||||
|
); |
||||
|
},); |
||||
|
} |
||||
|
|
||||
|
try{ |
||||
|
if (LocalStorage.readData(key: MyConstants.extractedAudio) != 'true') { |
||||
|
final File file = File(filePath); |
||||
|
final Directory directory = Directory( |
||||
|
'${StoragePath.documentDir.path}/$selectedLanguage/', |
||||
|
); |
||||
|
await ZipFile.extractToDirectory( |
||||
|
zipFile: file, |
||||
|
destinationDir: directory, |
||||
|
onExtracting: (zipEntry, progress) { |
||||
|
return ZipFileOperation.includeItem; |
||||
|
}, |
||||
|
).then((value) async { |
||||
|
await Future.wait([ |
||||
|
LocalStorage.saveData( |
||||
|
key: MyConstants.extractedAudio, |
||||
|
value: 'true', |
||||
|
), |
||||
|
file.delete(recursive: true), |
||||
|
]); |
||||
|
}); |
||||
|
} else { |
||||
|
streamController.add(DownloadEntity(percent: 50)); |
||||
|
await Future.delayed(Duration(milliseconds: 150)); |
||||
|
streamController.add(DownloadEntity(percent: 100)); |
||||
|
} |
||||
|
} catch (e){ |
||||
|
throw MyException(errorMessage: '$e'); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
@override |
||||
|
Future<void> saveLevels() async { |
||||
|
final String selectedLanguage = |
||||
|
LocalStorage.readData(key: MyConstants.selectLanguage) ?? 'fa'; |
||||
|
final Box<TotalDataEntity> data = Hive.box(MyConstants.levelBox); |
||||
|
final TotalDataEntity findData = data.values.singleWhere( |
||||
|
(e) => e.code == selectedLanguage, |
||||
|
orElse: () => TotalDataEntity(), |
||||
|
); |
||||
|
|
||||
|
if (findData.code != selectedLanguage) { |
||||
|
final response = await httpRequest.get( |
||||
|
path: MyApi.levels, |
||||
|
queryParameters: {'lang': selectedLanguage}, |
||||
|
); |
||||
|
final List<LevelEntity> levels = BaseResponse.getDataList<LevelEntity>( |
||||
|
response?['result'], |
||||
|
(json) => LevelModel.fromJson(json), |
||||
|
); |
||||
|
await Future.wait([ |
||||
|
data.add(TotalDataEntity(code: selectedLanguage, levels: levels)), |
||||
|
]); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
@override |
||||
|
Stream<DownloadEntity> loadingStream() => streamController.stream; |
||||
|
} |
@ -0,0 +1,50 @@ |
|||||
|
import 'package:flutter/foundation.dart'; |
||||
|
import 'package:hadi_hoda_flutter/core/error_handler/my_exception.dart'; |
||||
|
import 'package:hadi_hoda_flutter/core/params/no_params.dart'; |
||||
|
import 'package:hadi_hoda_flutter/core/utils/data_state.dart'; |
||||
|
import 'package:hadi_hoda_flutter/features/download/data/datasource/download_datasource.dart'; |
||||
|
import 'package:hadi_hoda_flutter/features/download/domain/entities/download_entity.dart'; |
||||
|
import 'package:hadi_hoda_flutter/features/download/domain/repository/download_repository.dart'; |
||||
|
|
||||
|
class DownloadRepositoryImpl implements IDownloadRepository { |
||||
|
final IDownloadDatasource datasource; |
||||
|
|
||||
|
const DownloadRepositoryImpl(this.datasource); |
||||
|
|
||||
|
@override |
||||
|
Future<DataState<NoParams, MyException>> getAudios() async { |
||||
|
try { |
||||
|
await datasource.getAudios(); |
||||
|
return DataState.success(NoParams()); |
||||
|
} on MyException catch (e) { |
||||
|
return DataState.error(e); |
||||
|
} catch (e) { |
||||
|
if (kDebugMode) { |
||||
|
rethrow; |
||||
|
} else { |
||||
|
return DataState.error(MyException(errorMessage: '$e')); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
@override |
||||
|
Future<DataState<NoParams, MyException>> saveLevels() async { |
||||
|
try { |
||||
|
await datasource.saveLevels(); |
||||
|
return DataState.success(NoParams()); |
||||
|
} on MyException catch (e) { |
||||
|
return DataState.error(e); |
||||
|
} catch (e) { |
||||
|
if (kDebugMode) { |
||||
|
rethrow; |
||||
|
} else { |
||||
|
return DataState.error(MyException(errorMessage: '$e')); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
@override |
||||
|
Stream<DownloadEntity> loadingStream() { |
||||
|
return datasource.loadingStream(); |
||||
|
} |
||||
|
} |
@ -0,0 +1,10 @@ |
|||||
|
import 'package:hadi_hoda_flutter/core/error_handler/my_exception.dart'; |
||||
|
import 'package:hadi_hoda_flutter/core/params/no_params.dart'; |
||||
|
import 'package:hadi_hoda_flutter/core/utils/data_state.dart'; |
||||
|
import 'package:hadi_hoda_flutter/features/download/domain/entities/download_entity.dart'; |
||||
|
|
||||
|
abstract class IDownloadRepository { |
||||
|
Future<DataState<NoParams, MyException>> getAudios(); |
||||
|
Future<DataState<NoParams, MyException>> saveLevels(); |
||||
|
Stream<DownloadEntity> loadingStream(); |
||||
|
} |
@ -0,0 +1,16 @@ |
|||||
|
import 'package:hadi_hoda_flutter/core/error_handler/my_exception.dart'; |
||||
|
import 'package:hadi_hoda_flutter/core/params/no_params.dart'; |
||||
|
import 'package:hadi_hoda_flutter/core/usecase/usecase.dart'; |
||||
|
import 'package:hadi_hoda_flutter/core/utils/data_state.dart'; |
||||
|
import 'package:hadi_hoda_flutter/features/download/domain/repository/download_repository.dart'; |
||||
|
|
||||
|
class GetAudiosUseCase implements UseCase<NoParams, NoParams> { |
||||
|
final IDownloadRepository repository; |
||||
|
|
||||
|
const GetAudiosUseCase(this.repository); |
||||
|
|
||||
|
@override |
||||
|
Future<DataState<NoParams, MyException>> call(NoParams params) { |
||||
|
return repository.getAudios(); |
||||
|
} |
||||
|
} |
@ -0,0 +1,12 @@ |
|||||
|
import 'package:hadi_hoda_flutter/features/download/domain/entities/download_entity.dart'; |
||||
|
import 'package:hadi_hoda_flutter/features/download/domain/repository/download_repository.dart'; |
||||
|
|
||||
|
class LoadingStreamAudioUseCase { |
||||
|
final IDownloadRepository repository; |
||||
|
|
||||
|
const LoadingStreamAudioUseCase(this.repository); |
||||
|
|
||||
|
Stream<DownloadEntity> call() { |
||||
|
return repository.loadingStream(); |
||||
|
} |
||||
|
} |
@ -0,0 +1,74 @@ |
|||||
|
import 'dart:async'; |
||||
|
|
||||
|
import 'package:bloc/bloc.dart'; |
||||
|
import 'package:go_router/go_router.dart'; |
||||
|
import 'package:hadi_hoda_flutter/core/params/no_params.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/context_provider.dart'; |
||||
|
import 'package:hadi_hoda_flutter/features/download/domain/entities/download_entity.dart'; |
||||
|
import 'package:hadi_hoda_flutter/features/download/domain/usecases/get_audios_usecase.dart'; |
||||
|
import 'package:hadi_hoda_flutter/features/download/domain/usecases/loading_stream_audio_usecase.dart'; |
||||
|
import 'package:hadi_hoda_flutter/features/download/domain/usecases/save_levels_usecase.dart'; |
||||
|
import 'package:hadi_hoda_flutter/features/download/presentation/bloc/download_event.dart'; |
||||
|
import 'package:hadi_hoda_flutter/features/download/presentation/bloc/download_state.dart'; |
||||
|
|
||||
|
class DownloadBloc extends Bloc<DownloadEvent, DownloadState> { |
||||
|
/// ------------constructor------------ |
||||
|
DownloadBloc( |
||||
|
this._getAudiosUseCase, |
||||
|
this._loadingStreamUseCase, |
||||
|
this._saveLevelsUseCase, |
||||
|
) |
||||
|
: super(const DownloadState()) { |
||||
|
on<GetAudiosEvent>(_getAudiosEvent); |
||||
|
on<SaveLevelsEvent>(_saveLevelsEvent); |
||||
|
loadingStream = _loadingStreamUseCase(); |
||||
|
} |
||||
|
|
||||
|
/// ------------UseCases------------ |
||||
|
final GetAudiosUseCase _getAudiosUseCase; |
||||
|
final SaveLevelsUseCase _saveLevelsUseCase; |
||||
|
final LoadingStreamAudioUseCase _loadingStreamUseCase; |
||||
|
|
||||
|
/// ------------Variables------------ |
||||
|
Stream<DownloadEntity> loadingStream = Stream.empty(); |
||||
|
|
||||
|
/// ------------Controllers------------ |
||||
|
|
||||
|
/// ------------Functions------------ |
||||
|
|
||||
|
/// ------------Api Calls------------ |
||||
|
FutureOr<void> _getAudiosEvent( |
||||
|
GetAudiosEvent event, |
||||
|
Emitter<DownloadState> emit, |
||||
|
) async { |
||||
|
emit(state.copyWith(getFilesStatus: BaseInit())); |
||||
|
await _getAudiosUseCase(NoParams()).then((value) { |
||||
|
value.fold( |
||||
|
(data) async { |
||||
|
add(SaveLevelsEvent()); |
||||
|
}, |
||||
|
(error) async { |
||||
|
emit(state.copyWith(getFilesStatus: BaseError(error.errorMessage))); |
||||
|
}, |
||||
|
); |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
FutureOr<void> _saveLevelsEvent( |
||||
|
SaveLevelsEvent event, |
||||
|
Emitter<DownloadState> emit, |
||||
|
) async { |
||||
|
await _saveLevelsUseCase(NoParams()).then((value) => |
||||
|
value.fold( |
||||
|
(data) async { |
||||
|
ContextProvider.context.goNamed(Routes.homePage); |
||||
|
}, |
||||
|
(error) { |
||||
|
emit(state.copyWith(getFilesStatus: BaseError(error.errorMessage))); |
||||
|
}, |
||||
|
), |
||||
|
); |
||||
|
} |
||||
|
} |
@ -0,0 +1,5 @@ |
|||||
|
sealed class DownloadEvent { |
||||
|
const DownloadEvent(); |
||||
|
} |
||||
|
class GetAudiosEvent extends DownloadEvent {} |
||||
|
class SaveLevelsEvent extends DownloadEvent {} |
@ -0,0 +1,15 @@ |
|||||
|
import 'package:hadi_hoda_flutter/core/status/base_status.dart'; |
||||
|
|
||||
|
class DownloadState { |
||||
|
final BaseStatus getFilesStatus; |
||||
|
|
||||
|
const DownloadState({this.getFilesStatus = const BaseInit()}); |
||||
|
|
||||
|
DownloadState copyWith({ |
||||
|
BaseStatus? getFilesStatus, |
||||
|
}) { |
||||
|
return DownloadState( |
||||
|
getFilesStatus: getFilesStatus ?? this.getFilesStatus, |
||||
|
); |
||||
|
} |
||||
|
} |
@ -0,0 +1,130 @@ |
|||||
|
import 'package:flutter/material.dart'; |
||||
|
import 'package:flutter_bloc/flutter_bloc.dart'; |
||||
|
import 'package:hadi_hoda_flutter/common_ui/resources/my_assets.dart'; |
||||
|
import 'package:hadi_hoda_flutter/common_ui/resources/my_colors.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/status/base_status.dart'; |
||||
|
import 'package:hadi_hoda_flutter/core/utils/convert_size.dart'; |
||||
|
import 'package:hadi_hoda_flutter/core/utils/my_image.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/widgets/error/error_state.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/download/presentation/bloc/download_state.dart'; |
||||
|
import 'package:hadi_hoda_flutter/features/download/presentation/ui/widgets/download_loading_widget.dart'; |
||||
|
|
||||
|
class DownloadPage extends StatefulWidget { |
||||
|
const DownloadPage({super.key}); |
||||
|
|
||||
|
@override |
||||
|
State<DownloadPage> createState() => _DownloadPageState(); |
||||
|
} |
||||
|
|
||||
|
class _DownloadPageState extends State<DownloadPage> { |
||||
|
@override |
||||
|
void initState() { |
||||
|
super.initState(); |
||||
|
context.read<DownloadBloc>().add(GetAudiosEvent()); |
||||
|
} |
||||
|
|
||||
|
@override |
||||
|
Widget build(BuildContext context) { |
||||
|
return Scaffold( |
||||
|
body: Container( |
||||
|
height: context.heightScreen, |
||||
|
width: context.widthScreen, |
||||
|
decoration: BoxDecoration( |
||||
|
gradient: LinearGradient( |
||||
|
begin: Alignment.topCenter, |
||||
|
end: Alignment.bottomCenter, |
||||
|
colors: [ |
||||
|
Color(0XFF00154C), |
||||
|
Color(0XFF150532), |
||||
|
], |
||||
|
), |
||||
|
image: DecorationImage( |
||||
|
image: AssetImage(MyAssets.pattern), |
||||
|
scale: 3, |
||||
|
repeat: ImageRepeat.repeat, |
||||
|
colorFilter: ColorFilter.mode( |
||||
|
Colors.white.withValues(alpha: 0.2), |
||||
|
BlendMode.srcIn, |
||||
|
), |
||||
|
), |
||||
|
), |
||||
|
child: BlocBuilder<DownloadBloc, DownloadState>( |
||||
|
buildWhen: (previous, current) => |
||||
|
previous.getFilesStatus != current.getFilesStatus, |
||||
|
builder: (context, state) { |
||||
|
if (state.getFilesStatus is BaseError) { |
||||
|
return Padding( |
||||
|
padding: EdgeInsets.symmetric( |
||||
|
vertical: MediaQuery.viewPaddingOf(context).bottom + MySpaces.s16, |
||||
|
horizontal: 60, |
||||
|
), |
||||
|
child: ErrorState( |
||||
|
onTap: () => context.read<DownloadBloc>().add(GetAudiosEvent()), |
||||
|
), |
||||
|
); |
||||
|
} else { |
||||
|
return Stack( |
||||
|
alignment: Alignment.center, |
||||
|
children: [ |
||||
|
_image(), |
||||
|
_text(context), |
||||
|
_loading(context), |
||||
|
], |
||||
|
); |
||||
|
} |
||||
|
} |
||||
|
), |
||||
|
), |
||||
|
); |
||||
|
} |
||||
|
|
||||
|
MyImage _image() { |
||||
|
return MyImage( |
||||
|
image: MyAssets.hadiHoda, |
||||
|
size: 200, |
||||
|
); |
||||
|
} |
||||
|
|
||||
|
Widget _text(BuildContext context) { |
||||
|
return PositionedDirectional( |
||||
|
bottom: 130, |
||||
|
child: Column( |
||||
|
spacing: MySpaces.s6, |
||||
|
children: [ |
||||
|
Text( |
||||
|
context.translate.please_wait, |
||||
|
style: Marhey.medium22.copyWith( |
||||
|
color: MyColors.white, |
||||
|
), |
||||
|
), |
||||
|
StreamBuilder<DownloadEntity>( |
||||
|
initialData: DownloadEntity(), |
||||
|
stream: context.read<DownloadBloc>().loadingStream, |
||||
|
builder: (context, snapshot) => Text( |
||||
|
'${context.translate.downloading_data} (${snapshot.data?.count.toMB ?? 0.0}mb / ${snapshot.data?.total.toMB ?? 0.0}mb)', |
||||
|
style: Marhey.medium12.copyWith( |
||||
|
color: MyColors.white, |
||||
|
), |
||||
|
), |
||||
|
), |
||||
|
], |
||||
|
), |
||||
|
); |
||||
|
} |
||||
|
|
||||
|
Positioned _loading(BuildContext context) { |
||||
|
return Positioned( |
||||
|
bottom: MediaQuery.viewPaddingOf(context).bottom + MySpaces.s16, |
||||
|
child: DownloadLoadingWidget( |
||||
|
loadingStream: context.read<DownloadBloc>().loadingStream, |
||||
|
), |
||||
|
); |
||||
|
} |
||||
|
} |
@ -0,0 +1,136 @@ |
|||||
|
import 'package:flutter/material.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/features/download/domain/entities/download_entity.dart'; |
||||
|
|
||||
|
|
||||
|
class DownloadLoadingWidget extends StatelessWidget { |
||||
|
const DownloadLoadingWidget({ |
||||
|
super.key, |
||||
|
this.loadingStream, |
||||
|
}); |
||||
|
|
||||
|
final Stream<DownloadEntity>? loadingStream; |
||||
|
|
||||
|
@override |
||||
|
Widget build(BuildContext context) { |
||||
|
return ClipPath( |
||||
|
clipper: BubbleClip(), |
||||
|
child: Container( |
||||
|
width: 300, |
||||
|
height: 60, |
||||
|
padding: EdgeInsets.symmetric( |
||||
|
vertical: MySpaces.s4, |
||||
|
horizontal: MySpaces.s2, |
||||
|
), |
||||
|
decoration: const BoxDecoration( |
||||
|
gradient: LinearGradient( |
||||
|
begin: Alignment(0, 1), // bottom |
||||
|
end: Alignment(0, -1), // top |
||||
|
colors: [ |
||||
|
Color(0xFFCADCFF), // #CADCFF |
||||
|
Colors.white, // #FFFFFF |
||||
|
], |
||||
|
), |
||||
|
), |
||||
|
child: StreamBuilder<DownloadEntity>( |
||||
|
initialData: DownloadEntity(), |
||||
|
stream: loadingStream, |
||||
|
builder: (context, snapshot) { |
||||
|
return Row( |
||||
|
children: [ |
||||
|
Expanded( |
||||
|
flex: 85, |
||||
|
child: ClipPath( |
||||
|
clipper: BubbleClip(), |
||||
|
child: AnimatedContainer( |
||||
|
duration: const Duration(milliseconds: 300), |
||||
|
padding: EdgeInsetsDirectional.only( |
||||
|
end: 260 - ((snapshot.data?.percent ?? 0) * 260 / 100), |
||||
|
), |
||||
|
decoration: BoxDecoration( |
||||
|
color: Color(0xFF1F59BD).withValues(alpha: 0.25), |
||||
|
), |
||||
|
child: ClipPath( |
||||
|
clipper: BubbleClip(), |
||||
|
child: Container( |
||||
|
decoration: BoxDecoration( |
||||
|
gradient: RadialGradient( |
||||
|
radius: 2, |
||||
|
colors: [ |
||||
|
Color(0xFFFFBD00), // #CADCFF |
||||
|
Color(0xFFFF772C), // #CADCFF |
||||
|
], |
||||
|
), |
||||
|
), |
||||
|
), |
||||
|
), |
||||
|
), |
||||
|
), |
||||
|
), |
||||
|
Expanded( |
||||
|
flex: 15, |
||||
|
child: Center( |
||||
|
child: Text( |
||||
|
'${snapshot.data?.percent?.toInt() ?? 0}%', |
||||
|
style: DinoKids.regular17.copyWith( |
||||
|
color: Color(0XFF6E83A8), |
||||
|
), |
||||
|
), |
||||
|
), |
||||
|
), |
||||
|
], |
||||
|
); |
||||
|
} |
||||
|
), |
||||
|
), |
||||
|
); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
class BubbleClip extends CustomClipper<Path> { |
||||
|
@override |
||||
|
Path getClip(Size size) { |
||||
|
// Original SVG viewBox: 334 x 60 |
||||
|
const double w0 = 334.0; |
||||
|
const double h0 = 60.0; |
||||
|
final sx = size.width / w0; |
||||
|
final sy = size.height / h0; |
||||
|
|
||||
|
// SVG path: |
||||
|
// M9.82057 10.3597 |
||||
|
// C -1.70838 17.1589 -3.47995 44.4301 6.60447 53.1719 |
||||
|
// C 16.0075 61.291 305.076 61.9385 323.201 53.4956 |
||||
|
// C 341.326 45.0527 332.116 8.04571 324.829 5.7273 |
||||
|
// C 307.985 -2.06805 28.6539 -0.77294 9.82057 10.3597 |
||||
|
// Z |
||||
|
final p = Path() |
||||
|
..moveTo(9.82057 * sx, 10.3597 * sy) |
||||
|
..cubicTo( |
||||
|
-1.70838 * sx, 17.1589 * sy, |
||||
|
-3.47995 * sx, 44.4301 * sy, |
||||
|
6.60447 * sx, 53.1719 * sy, |
||||
|
) |
||||
|
..cubicTo( |
||||
|
16.0075 * sx, 61.291 * sy, |
||||
|
305.076 * sx, 61.9385 * sy, |
||||
|
323.201 * sx, 53.4956 * sy, |
||||
|
) |
||||
|
..cubicTo( |
||||
|
341.326 * sx, 45.0527 * sy, |
||||
|
332.116 * sx, 8.04571 * sy, |
||||
|
324.829 * sx, 5.7273 * sy, |
||||
|
) |
||||
|
..cubicTo( |
||||
|
307.985 * sx, -2.06805 * sy, |
||||
|
28.6539 * sx, -0.77294 * sy, |
||||
|
9.82057 * sx, 10.3597 * sy, |
||||
|
) |
||||
|
..close(); |
||||
|
|
||||
|
return p; |
||||
|
} |
||||
|
|
||||
|
@override |
||||
|
bool shouldReclip(covariant CustomClipper<Path> oldClipper) => false; |
||||
|
} |
@ -1,10 +1,9 @@ |
|||||
import 'package:hadi_hoda_flutter/core/error_handler/my_exception.dart'; |
import 'package:hadi_hoda_flutter/core/error_handler/my_exception.dart'; |
||||
import 'package:hadi_hoda_flutter/core/params/no_params.dart'; |
import 'package:hadi_hoda_flutter/core/params/no_params.dart'; |
||||
import 'package:hadi_hoda_flutter/core/utils/data_state.dart'; |
import 'package:hadi_hoda_flutter/core/utils/data_state.dart'; |
||||
import 'package:hadi_hoda_flutter/features/intro/domain/entities/download_entity.dart'; |
|
||||
|
import 'package:hadi_hoda_flutter/features/download/domain/entities/download_entity.dart'; |
||||
|
|
||||
abstract class IIntroRepository { |
abstract class IIntroRepository { |
||||
Future<DataState<NoParams, MyException>> getFiles(); |
|
||||
Future<DataState<NoParams, MyException>> saveLevels(); |
|
||||
|
Future<DataState<NoParams, MyException>> getImages(); |
||||
Stream<DownloadEntity> loadingStream(); |
Stream<DownloadEntity> loadingStream(); |
||||
} |
} |
@ -1,5 +1,4 @@ |
|||||
sealed class IntroEvent { |
sealed class IntroEvent { |
||||
const IntroEvent(); |
const IntroEvent(); |
||||
} |
} |
||||
class GetFilesEvent extends IntroEvent {} |
|
||||
class SaveLevelsEvent extends IntroEvent {} |
|
||||
|
class GetImagesEvent extends IntroEvent {} |
Write
Preview
Loading…
Cancel
Save
Reference in new issue