2024-02-08 10:23:47 +01:00
|
|
|
import 'dart:convert';
|
2023-09-17 14:55:46 +03:30
|
|
|
import 'package:flutter/gestures.dart';
|
2024-02-22 09:46:43 +01:00
|
|
|
import 'package:timezone_to_country/timezone_to_country.dart';
|
2024-02-08 10:23:47 +01:00
|
|
|
|
2023-09-17 00:24:00 +03:30
|
|
|
import 'package:flutter/material.dart';
|
2023-10-07 20:22:21 +03:30
|
|
|
import 'package:flutter_hooks/flutter_hooks.dart';
|
2023-09-17 00:24:00 +03:30
|
|
|
import 'package:gap/gap.dart';
|
2023-12-22 14:16:24 +03:30
|
|
|
import 'package:hiddify/core/analytics/analytics_controller.dart';
|
2024-02-08 10:23:47 +01:00
|
|
|
import 'package:hiddify/core/localization/locale_preferences.dart';
|
2023-12-01 12:56:24 +03:30
|
|
|
import 'package:hiddify/core/localization/translations.dart';
|
|
|
|
|
import 'package:hiddify/core/model/constants.dart';
|
2024-02-08 10:23:47 +01:00
|
|
|
import 'package:hiddify/core/model/region.dart';
|
2023-12-01 12:56:24 +03:30
|
|
|
import 'package:hiddify/core/preferences/general_preferences.dart';
|
2023-11-09 15:23:48 +03:30
|
|
|
import 'package:hiddify/features/common/general_pref_tiles.dart';
|
2023-09-17 00:24:00 +03:30
|
|
|
import 'package:hiddify/gen/assets.gen.dart';
|
|
|
|
|
import 'package:hiddify/utils/utils.dart';
|
|
|
|
|
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
2024-02-08 10:23:47 +01:00
|
|
|
import 'package:http/http.dart' as http;
|
2023-09-17 00:24:00 +03:30
|
|
|
import 'package:sliver_tools/sliver_tools.dart';
|
|
|
|
|
|
|
|
|
|
class IntroPage extends HookConsumerWidget with PresLogger {
|
|
|
|
|
const IntroPage({super.key});
|
|
|
|
|
|
|
|
|
|
@override
|
|
|
|
|
Widget build(BuildContext context, WidgetRef ref) {
|
|
|
|
|
final t = ref.watch(translationsProvider);
|
|
|
|
|
|
2023-10-07 20:22:21 +03:30
|
|
|
final isStarting = useState(false);
|
2024-02-08 10:23:47 +01:00
|
|
|
autoSelectRegion(ref)
|
|
|
|
|
.then((value) => loggy.debug("Auto Region selection finished!"));
|
2024-02-01 11:19:39 +03:30
|
|
|
return Scaffold(
|
|
|
|
|
body: SafeArea(
|
|
|
|
|
child: CustomScrollView(
|
2023-11-10 15:35:44 +03:30
|
|
|
shrinkWrap: true,
|
|
|
|
|
slivers: [
|
|
|
|
|
SliverToBoxAdapter(
|
|
|
|
|
child: SizedBox(
|
|
|
|
|
width: 224,
|
|
|
|
|
height: 224,
|
|
|
|
|
child: Padding(
|
|
|
|
|
padding: const EdgeInsets.all(24),
|
|
|
|
|
child: Assets.images.logo.svg(),
|
|
|
|
|
),
|
2023-09-17 00:24:00 +03:30
|
|
|
),
|
|
|
|
|
),
|
2023-11-10 15:35:44 +03:30
|
|
|
SliverCrossAxisConstrained(
|
|
|
|
|
maxCrossAxisExtent: 368,
|
|
|
|
|
child: MultiSliver(
|
|
|
|
|
children: [
|
|
|
|
|
const LocalePrefTile(),
|
|
|
|
|
const SliverGap(4),
|
|
|
|
|
const RegionPrefTile(),
|
|
|
|
|
const SliverGap(4),
|
|
|
|
|
const EnableAnalyticsPrefTile(),
|
|
|
|
|
const SliverGap(4),
|
|
|
|
|
Padding(
|
|
|
|
|
padding: const EdgeInsets.symmetric(horizontal: 16),
|
|
|
|
|
child: Text.rich(
|
|
|
|
|
t.intro.termsAndPolicyCaution(
|
|
|
|
|
tap: (text) => TextSpan(
|
|
|
|
|
text: text,
|
|
|
|
|
style: const TextStyle(color: Colors.blue),
|
|
|
|
|
recognizer: TapGestureRecognizer()
|
|
|
|
|
..onTap = () async {
|
|
|
|
|
await UriUtils.tryLaunch(
|
|
|
|
|
Uri.parse(Constants.termsAndConditionsUrl),
|
|
|
|
|
);
|
|
|
|
|
},
|
|
|
|
|
),
|
2023-09-17 14:55:46 +03:30
|
|
|
),
|
2023-11-10 15:35:44 +03:30
|
|
|
style: Theme.of(context).textTheme.bodySmall,
|
2023-09-17 14:55:46 +03:30
|
|
|
),
|
|
|
|
|
),
|
2023-11-10 15:35:44 +03:30
|
|
|
Padding(
|
|
|
|
|
padding: const EdgeInsets.symmetric(
|
|
|
|
|
horizontal: 16,
|
|
|
|
|
vertical: 24,
|
|
|
|
|
),
|
|
|
|
|
child: FilledButton(
|
|
|
|
|
onPressed: () async {
|
|
|
|
|
if (isStarting.value) return;
|
|
|
|
|
isStarting.value = true;
|
2023-12-23 11:01:48 +03:30
|
|
|
if (!ref
|
|
|
|
|
.read(analyticsControllerProvider)
|
|
|
|
|
.requireValue) {
|
2023-11-10 15:35:44 +03:30
|
|
|
loggy.info("disabling analytics per user request");
|
|
|
|
|
try {
|
2023-12-22 14:16:24 +03:30
|
|
|
await ref
|
|
|
|
|
.read(analyticsControllerProvider.notifier)
|
|
|
|
|
.disableAnalytics();
|
2023-11-10 15:35:44 +03:30
|
|
|
} catch (error, stackTrace) {
|
|
|
|
|
loggy.error(
|
|
|
|
|
"could not disable analytics",
|
|
|
|
|
error,
|
|
|
|
|
stackTrace,
|
|
|
|
|
);
|
|
|
|
|
}
|
2023-10-04 18:06:48 +03:30
|
|
|
}
|
2023-11-10 15:35:44 +03:30
|
|
|
await ref
|
|
|
|
|
.read(introCompletedProvider.notifier)
|
|
|
|
|
.update(true);
|
|
|
|
|
},
|
|
|
|
|
child: isStarting.value
|
|
|
|
|
? LinearProgressIndicator(
|
|
|
|
|
backgroundColor: Colors.transparent,
|
|
|
|
|
color: Theme.of(context).colorScheme.onSurface,
|
|
|
|
|
)
|
|
|
|
|
: Text(t.intro.start),
|
|
|
|
|
),
|
2023-09-17 00:24:00 +03:30
|
|
|
),
|
2023-11-10 15:35:44 +03:30
|
|
|
],
|
|
|
|
|
),
|
2023-09-17 00:24:00 +03:30
|
|
|
),
|
2023-11-10 15:35:44 +03:30
|
|
|
],
|
|
|
|
|
),
|
2023-09-17 00:24:00 +03:30
|
|
|
),
|
|
|
|
|
);
|
|
|
|
|
}
|
2024-02-08 10:23:47 +01:00
|
|
|
|
|
|
|
|
Future<void> autoSelectRegion(WidgetRef ref) async {
|
2024-02-22 09:46:43 +01:00
|
|
|
try {
|
|
|
|
|
final countryCode = await TimeZoneToCountry.getLocalCountryCode();
|
|
|
|
|
final regionLocale = _getRegionLocale(countryCode);
|
2024-02-08 10:23:47 +01:00
|
|
|
loggy.debug(
|
2024-02-22 09:46:43 +01:00
|
|
|
'Timezone Region: ${regionLocale.region} Locale: ${regionLocale.locale}',
|
|
|
|
|
);
|
2024-02-08 10:23:47 +01:00
|
|
|
await ref
|
|
|
|
|
.read(regionNotifierProvider.notifier)
|
|
|
|
|
.update(regionLocale.region);
|
|
|
|
|
await ref
|
|
|
|
|
.read(localePreferencesProvider.notifier)
|
|
|
|
|
.changeLocale(regionLocale.locale);
|
2024-02-22 09:46:43 +01:00
|
|
|
} catch (e) {
|
|
|
|
|
loggy.warning('Could not get the local country code based on timezone');
|
|
|
|
|
}
|
|
|
|
|
try {
|
|
|
|
|
final response = await http.get(Uri.parse('https://api.ip.sb/json/'));
|
|
|
|
|
|
|
|
|
|
if (response.statusCode == 200) {
|
|
|
|
|
final jsonData = jsonDecode(response.body);
|
|
|
|
|
final regionLocale =
|
|
|
|
|
_getRegionLocale(jsonData['country']?.toString() ?? "");
|
|
|
|
|
|
|
|
|
|
loggy.debug(
|
|
|
|
|
'Region: ${regionLocale.region} Locale: ${regionLocale.locale}',
|
|
|
|
|
);
|
|
|
|
|
await ref
|
|
|
|
|
.read(regionNotifierProvider.notifier)
|
|
|
|
|
.update(regionLocale.region);
|
|
|
|
|
await ref
|
|
|
|
|
.read(localePreferencesProvider.notifier)
|
|
|
|
|
.changeLocale(regionLocale.locale);
|
|
|
|
|
} else {
|
|
|
|
|
loggy.warning('Request failed with status: ${response.statusCode}');
|
|
|
|
|
}
|
|
|
|
|
} catch (e) {
|
|
|
|
|
loggy.warning('Could not get the local country code from ip');
|
2024-02-08 10:23:47 +01:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
RegionLocale _getRegionLocale(String country) {
|
2024-02-22 09:46:43 +01:00
|
|
|
switch (country.toUpperCase()) {
|
2024-02-08 10:23:47 +01:00
|
|
|
case "IR":
|
|
|
|
|
return RegionLocale(Region.ir, AppLocale.fa);
|
|
|
|
|
case "CN":
|
|
|
|
|
return RegionLocale(Region.cn, AppLocale.zhCn);
|
|
|
|
|
case "RU":
|
|
|
|
|
return RegionLocale(Region.ru, AppLocale.ru);
|
|
|
|
|
case "AF":
|
|
|
|
|
return RegionLocale(Region.af, AppLocale.fa);
|
|
|
|
|
default:
|
|
|
|
|
return RegionLocale(Region.other, AppLocale.en);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
class RegionLocale {
|
|
|
|
|
final Region region;
|
|
|
|
|
final AppLocale locale;
|
|
|
|
|
|
|
|
|
|
RegionLocale(this.region, this.locale);
|
2023-09-17 00:24:00 +03:30
|
|
|
}
|