Skip to content

Commit

Permalink
#17 explicitly explain to a user why do we request those google permi…
Browse files Browse the repository at this point in the history
…ssions
  • Loading branch information
Denis Zhdanov committed Jan 1, 2025
1 parent 5d287ca commit 4b9c62b
Show file tree
Hide file tree
Showing 9 changed files with 328 additions and 157 deletions.
36 changes: 36 additions & 0 deletions ios/Runner.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -476,6 +476,7 @@
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
CLANG_WARN_STRICT_PROTOTYPES = YES;
CLANG_WARN_SUSPICIOUS_MOVE = YES;
Expand Down Expand Up @@ -510,17 +511,28 @@
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CLANG_ENABLE_MODULES = YES;
CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
DEVELOPMENT_TEAM = B73F2CBGJ7;
ENABLE_BITCODE = NO;
INFOPLIST_FILE = Runner/Info.plist;
INFOPLIST_KEY_CFBundleDisplayName = "Chrono Sheet";
INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.productivity";
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
);
PRODUCT_BUNDLE_IDENTIFIER = tech.harmonysoft.oss.chronoSheet;
PRODUCT_NAME = "$(TARGET_NAME)";
PROVISIONING_PROFILE_SPECIFIER = "";
SUPPORTED_PLATFORMS = "iphoneos iphonesimulator";
SUPPORTS_MACCATALYST = NO;
SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = NO;
SUPPORTS_XR_DESIGNED_FOR_IPHONE_IPAD = NO;
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
SWIFT_VERSION = 5.0;
TARGETED_DEVICE_FAMILY = "1,2";
VERSIONING_SYSTEM = "apple-generic";
};
name = Profile;
Expand Down Expand Up @@ -599,6 +611,7 @@
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
CLANG_WARN_STRICT_PROTOTYPES = YES;
CLANG_WARN_SUSPICIOUS_MOVE = YES;
Expand Down Expand Up @@ -656,6 +669,7 @@
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
CLANG_WARN_STRICT_PROTOTYPES = YES;
CLANG_WARN_SUSPICIOUS_MOVE = YES;
Expand Down Expand Up @@ -692,18 +706,29 @@
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CLANG_ENABLE_MODULES = YES;
CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
DEVELOPMENT_TEAM = B73F2CBGJ7;
ENABLE_BITCODE = NO;
INFOPLIST_FILE = Runner/Info.plist;
INFOPLIST_KEY_CFBundleDisplayName = "Chrono Sheet";
INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.productivity";
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
);
PRODUCT_BUNDLE_IDENTIFIER = tech.harmonysoft.oss.chronoSheet;
PRODUCT_NAME = "$(TARGET_NAME)";
PROVISIONING_PROFILE_SPECIFIER = "";
SUPPORTED_PLATFORMS = "iphoneos iphonesimulator";
SUPPORTS_MACCATALYST = NO;
SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = NO;
SUPPORTS_XR_DESIGNED_FOR_IPHONE_IPAD = NO;
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
SWIFT_VERSION = 5.0;
TARGETED_DEVICE_FAMILY = "1,2";
VERSIONING_SYSTEM = "apple-generic";
};
name = Debug;
Expand All @@ -714,17 +739,28 @@
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CLANG_ENABLE_MODULES = YES;
CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
DEVELOPMENT_TEAM = B73F2CBGJ7;
ENABLE_BITCODE = NO;
INFOPLIST_FILE = Runner/Info.plist;
INFOPLIST_KEY_CFBundleDisplayName = "Chrono Sheet";
INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.productivity";
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
);
PRODUCT_BUNDLE_IDENTIFIER = tech.harmonysoft.oss.chronoSheet;
PRODUCT_NAME = "$(TARGET_NAME)";
PROVISIONING_PROFILE_SPECIFIER = "";
SUPPORTED_PLATFORMS = "iphoneos iphonesimulator";
SUPPORTS_MACCATALYST = NO;
SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = NO;
SUPPORTS_XR_DESIGNED_FOR_IPHONE_IPAD = NO;
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
SWIFT_VERSION = 5.0;
TARGETED_DEVICE_FAMILY = "1,2";
VERSIONING_SYSTEM = "apple-generic";
};
name = Release;
Expand Down
216 changes: 129 additions & 87 deletions lib/category/widget/category_widget.dart
Original file line number Diff line number Diff line change
@@ -1,115 +1,157 @@
import 'package:chrono_sheet/category/model/category.dart';
import 'package:chrono_sheet/category/state/categories_state.dart';
import 'package:chrono_sheet/file/state/files_state.dart';
import 'package:chrono_sheet/generated/app_localizations.dart';
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';

class CategoryWidget extends ConsumerWidget {
const CategoryWidget({super.key});

void _addCategory(BuildContext context, FileCategories categoriesNotifier) {
final controller = TextEditingController();
final hasCategoryNameNotifier = ValueNotifier(false);
controller.addListener(() => hasCategoryNameNotifier.value = controller.text.trim().isNotEmpty);
final l10n = AppLocalizations.of(context);
showDialog(
context: context,
builder: (context) => AlertDialog(
title: Text(l10n.titleAddNewCategory),
content: TextField(
controller: controller,
decoration: InputDecoration(
labelText: l10n.labelCategoryName,
border: OutlineInputBorder(),
),
@override
Widget build(BuildContext context, WidgetRef ref) {
final asyncFiles = ref.watch(filesInfoHolderProvider);
return Container(
decoration: BoxDecoration(
border: Border(
bottom: BorderSide(),
),
actions: [
TextButton(
onPressed: () => Navigator.of(context).pop(),
child: Text(l10n.textCancel),
),
ValueListenableBuilder(
valueListenable: hasCategoryNameNotifier,
builder: (context, enabled, child) => ElevatedButton(
onPressed: enabled
? () {
final categoryName = controller.text;
categoriesNotifier.select(Category(categoryName));
Navigator.of(context).pop();
}
: null,
child: Text(l10n.textAdd),
),
child: asyncFiles.maybeWhen(
data: (files) =>
files.operationInProgress == FileOperation.creation ? FileCreationWidget() : NoFileCreationWidget(),
orElse: () => NoFileCreationWidget(),
),
);
}
}

class FileCreationWidget extends StatelessWidget {
const FileCreationWidget({super.key});

@override
Widget build(BuildContext context) {
final theme = Theme.of(context);
final localization = AppLocalizations.of(context);
return Row(
children: [
IconButton(
onPressed: null,
icon: Icon(Icons.add),
),
Expanded(
child: Center(
child: Text(
localization.progressFileCreationInProgress,
style: TextStyle(color: theme.disabledColor),
),
),
],
),
),
DisabledPopupMenuButtonWidget(),
],
);
}
}

class NoFileCreationWidget extends ConsumerWidget {
const NoFileCreationWidget({super.key});

@override
Widget build(BuildContext context, WidgetRef ref) {
final asyncInfo = ref.watch(fileCategoriesProvider);
final asyncCategories = ref.watch(fileCategoriesProvider);
final theme = Theme.of(context);
final localization = AppLocalizations.of(context);
return Container(
decoration: BoxDecoration(
border: Border(
bottom: BorderSide(),
return Row(
children: [
IconButton(
onPressed: () => _addCategory(context, ref.read(fileCategoriesProvider.notifier)),
icon: Icon(Icons.add),
),
),
child: Row(
children: [
IconButton(
onPressed: () => _addCategory(context, ref.read(fileCategoriesProvider.notifier)),
icon: Icon(Icons.add),
),
Expanded(
child: asyncInfo.when(
data: (data) => Container(
color: Colors.transparent,
child: Center(
child: Text(
data.selected?.name ?? localization.hintTapToCreateCategory,
style: data.selected == null ? TextStyle(color: theme.disabledColor) : null,
),
),
Expanded(
child: Center(
child: asyncCategories.when(
data: (data) => Text(
data.selected?.name ?? localization.hintCreateCategory,
style: data.selected == null ? TextStyle(color: theme.disabledColor) : null,
),
error: (_, __) => Center(
child: Text(
localization.errorCanNotParseCategories,
style: TextStyle(color: theme.disabledColor),
),
error: (_, __) => Text(
localization.errorCanNotParseCategories,
style: TextStyle(color: theme.disabledColor),
),
loading: () => Center(
child: Text(
localization.progressParsingCategories,
style: TextStyle(color: theme.disabledColor),
),
loading: () => Text(
localization.progressParsingCategories,
style: TextStyle(color: theme.disabledColor),
),
),
),
asyncInfo.when(
data: (data) => PopupMenuButton<Category>(
icon: Icon(Icons.arrow_drop_down),
onSelected: (category) => ref.read(fileCategoriesProvider.notifier).select(category),
itemBuilder: (context) => data.categories.map((c) {
return PopupMenuItem(
value: c,
child: Text(c.name),
);
}).toList(),
),
error: (_, __) => PopupMenuButton(
icon: Icon(Icons.arrow_drop_down),
itemBuilder: (context) => [],
),
loading: () => PopupMenuButton(
icon: Icon(Icons.arrow_drop_down),
itemBuilder: (context) => [],
),
),
],
),
),
asyncCategories.maybeWhen(
data: (data) => data.categories.isEmpty
? DisabledPopupMenuButtonWidget()
: PopupMenuButton<Category>(
icon: Icon(Icons.arrow_drop_down),
onSelected: (category) => ref.read(fileCategoriesProvider.notifier).select(category),
itemBuilder: (context) => data.categories.map((c) {
return PopupMenuItem(
value: c,
child: Text(c.name),
);
}).toList(),
),
orElse: () => DisabledPopupMenuButtonWidget(),
),
],
);
}
}

class DisabledPopupMenuButtonWidget extends StatelessWidget {
const DisabledPopupMenuButtonWidget({super.key});

@override
Widget build(BuildContext context) {
return PopupMenuButton(
icon: Icon(Icons.arrow_drop_down),
itemBuilder: (context) => [],
);
}
}

void _addCategory(BuildContext context, FileCategories categoriesNotifier) {
final controller = TextEditingController();
final hasCategoryNameNotifier = ValueNotifier(false);
controller.addListener(() => hasCategoryNameNotifier.value = controller.text.trim().isNotEmpty);
final l10n = AppLocalizations.of(context);
showDialog(
context: context,
builder: (context) => AlertDialog(
title: Text(l10n.titleAddNewCategory),
content: TextField(
controller: controller,
decoration: InputDecoration(
labelText: l10n.labelCategoryName,
border: OutlineInputBorder(),
),
),
actions: [
TextButton(
onPressed: () => Navigator.of(context).pop(),
child: Text(l10n.textCancel),
),
ValueListenableBuilder(
valueListenable: hasCategoryNameNotifier,
builder: (context, enabled, child) => ElevatedButton(
onPressed: enabled
? () {
final categoryName = controller.text;
categoriesNotifier.select(Category(categoryName));
Navigator.of(context).pop();
}
: null,
child: Text(l10n.textAdd),
),
),
],
),
);
}
11 changes: 11 additions & 0 deletions lib/file/state/files_state.dart
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import 'package:chrono_sheet/file/model/google_file.dart';
import 'package:chrono_sheet/google/state/google_login_state.dart';
import 'package:riverpod_annotation/riverpod_annotation.dart';
import 'package:shared_preferences/shared_preferences.dart';
import '../../logging/logging.dart';
Expand Down Expand Up @@ -48,6 +49,16 @@ class FilesInfoHolder extends _$FilesInfoHolder {

@override
Future<FilesInfo> build() async {
final loginState = ref.watch(loginStateProvider);
switch (loginState) {
case AsyncData(value:final loggedIn):
if (!loggedIn) {
return FilesInfo();
}
default:
return FilesInfo();
}

var selected = _deserialize(await _prefs.getString(_Key.selected));
if (selected == null) {
return FilesInfo();
Expand Down
Loading

0 comments on commit 4b9c62b

Please sign in to comment.