feat(app): choose a studyset's licence, its year and holder
The studyset sheet gains Licence, as before: "Help me choose" asks three questions (keep any rights? commercial use? adaptations, alike?) and recommends one of the six current Creative Commons licences or CC0, with what it lets others do and a link to its terms; the licence field suggests well-known licences and takes any other as typed, kept in one spelling (`cc by-nc-sa 3.0 us` is `CC-BY-NC-SA-3.0-US`); the year sits at its end (1000 to next year), the holder below. An older or ported Creative Commons licence gets a note naming its current version. A new sub-studyset starts with the licence of the one it is in. Left out until publishing exists: the warnings about sub-studysets' licences that a parent's doesn't cover. The licence shows for every studyset, as there is no visibility yet. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01MMm7usAo6q9ZZRoPkWjoxi Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
This commit is contained in:
parent
b1ef9b9d3a
commit
bd6016ee1b
12 changed files with 1385 additions and 9 deletions
|
|
@ -314,6 +314,45 @@
|
|||
"studysetSheetFrontLanguage": "Sprache der Vorderseite",
|
||||
"studysetSheetBackLanguage": "Sprache der Rückseite",
|
||||
"studysetSheetLanguageNotSet": "Nicht festgelegt",
|
||||
"studysetSheetLicense": "Lizenz",
|
||||
"studysetSheetLicenseHelp": "Hilf mir bei der Wahl",
|
||||
"studysetSheetLicenseLabel": "Lizenz",
|
||||
"studysetSheetLicenseHelper": "Unter welchen Bedingungen andere den Inhalt nutzen dürfen, unter dem Lernset mit einem Link darauf angezeigt. Beispiele: CC-BY-4.0, CC-BY-SA-4.0, MIT, All Rights Reserved.",
|
||||
"studysetSheetLicenseYear": "Jahr",
|
||||
"studysetSheetLicenseYearHelper": "Das Jahr des Urheberrechts, angezeigt als „© 2019 Name“. Optional.",
|
||||
"studysetSheetLicenseYearInvalid": "Gib ein Jahr zwischen {min} und {max} ein.",
|
||||
"@studysetSheetLicenseYearInvalid": {"placeholders": {"min": {"type": "int"}, "max": {"type": "int"}}},
|
||||
"studysetSheetLicenseHolder": "Rechteinhaber",
|
||||
"studysetSheetLicenseHolderHelper": "Wer genannt wird: ein Name oder eine Organisation.",
|
||||
"studysetSheetLicenseNewer": "{license} ist eine ältere Creative-Commons-Lizenz. Ihre aktuelle Version, {current}, passt vielleicht besser.",
|
||||
"@studysetSheetLicenseNewer": {"placeholders": {"license": {"type": "String"}, "current": {"type": "String"}}},
|
||||
"licenseChooserTitle": "Lizenz wählen",
|
||||
"licenseChooserSubtitle": "Ein paar Fragen, um die Creative-Commons-Lizenz zu finden, die dazu passt, wie du dein Werk teilen möchtest.",
|
||||
"licenseChooserIntroPrompt": "Möchtest du Rechte an deinem Werk behalten?",
|
||||
"licenseChooserIntroYes": "Ja, unter einer Lizenz teilen",
|
||||
"licenseChooserIntroYesDetail": "Andere müssen die Bedingungen einhalten, die du als Nächstes wählst.",
|
||||
"licenseChooserIntroNo": "Nein, als Gemeingut freigeben",
|
||||
"licenseChooserIntroNoDetail": "CC0: Jeder darf es für alles nutzen, ohne Bedingungen.",
|
||||
"licenseChooserCommercialPrompt": "Dürfen andere dein Werk kommerziell nutzen?",
|
||||
"licenseChooserYes": "Ja",
|
||||
"licenseChooserCommercialYesDetail": "Auch mit Gewinnabsicht.",
|
||||
"licenseChooserNo": "Nein",
|
||||
"licenseChooserCommercialNoDetail": "Nur nicht-kommerzielle Nutzung (NC).",
|
||||
"licenseChooserAdaptationsPrompt": "Dürfen andere dein Werk bearbeiten oder darauf aufbauen?",
|
||||
"licenseChooserAdaptationsYesDetail": "Bearbeitungen und Remixe sind erlaubt.",
|
||||
"licenseChooserAdaptationsShareAlike": "Ja, unter gleichen Bedingungen",
|
||||
"licenseChooserAdaptationsShareAlikeDetail": "Bearbeitungen stehen unter derselben Lizenz (SA).",
|
||||
"licenseChooserAdaptationsNoDetail": "Nur Weitergabe, keine Bearbeitungen (ND).",
|
||||
"licenseChooserBack": "Zurück",
|
||||
"licenseChooserRecommended": "Empfohlene Lizenz",
|
||||
"licenseChooserUse": "Diese Lizenz verwenden",
|
||||
"licenseSummaryCc0": "Jeder darf es für alles nutzen, ohne Bedingungen und ohne Nennung.",
|
||||
"licenseSummaryBy": "Andere dürfen es teilen und bearbeiten, auch kommerziell, solange sie dich nennen.",
|
||||
"licenseSummaryBySa": "Andere dürfen es teilen und bearbeiten, auch kommerziell, wenn sie dich nennen und ihre Bearbeitungen genauso lizenzieren.",
|
||||
"licenseSummaryByNc": "Andere dürfen es für nicht-kommerzielle Zwecke teilen und bearbeiten, mit Nennung.",
|
||||
"licenseSummaryByNcSa": "Andere dürfen es nicht-kommerziell teilen und bearbeiten, mit Nennung und derselben Lizenz für Bearbeitungen.",
|
||||
"licenseSummaryByNd": "Andere dürfen es teilen, auch kommerziell, mit Nennung, aber keine Bearbeitungen weitergeben.",
|
||||
"licenseSummaryByNcNd": "Andere dürfen es nicht-kommerziell teilen, mit Nennung, aber nicht bearbeiten: die strengste Creative-Commons-Lizenz.",
|
||||
"languageArabic": "Arabisch",
|
||||
"languageGerman": "Deutsch",
|
||||
"languageGreek": "Griechisch",
|
||||
|
|
|
|||
|
|
@ -346,6 +346,45 @@
|
|||
"studysetSheetFrontLanguage": "Front language",
|
||||
"studysetSheetBackLanguage": "Back language",
|
||||
"studysetSheetLanguageNotSet": "Not set",
|
||||
"studysetSheetLicense": "Licence",
|
||||
"studysetSheetLicenseHelp": "Help me choose",
|
||||
"studysetSheetLicenseLabel": "Licence",
|
||||
"studysetSheetLicenseHelper": "The terms others may use the content under, shown below the studyset with a link to them. Examples: CC-BY-4.0, CC-BY-SA-4.0, MIT, All Rights Reserved.",
|
||||
"studysetSheetLicenseYear": "Year",
|
||||
"studysetSheetLicenseYearHelper": "The copyright year, shown as “© 2019 Name”. Optional.",
|
||||
"studysetSheetLicenseYearInvalid": "Enter a year between {min} and {max}.",
|
||||
"@studysetSheetLicenseYearInvalid": {"placeholders": {"min": {"type": "int"}, "max": {"type": "int"}}},
|
||||
"studysetSheetLicenseHolder": "Licence holder",
|
||||
"studysetSheetLicenseHolderHelper": "Who to credit: a name or an organisation.",
|
||||
"studysetSheetLicenseNewer": "{license} is an older Creative Commons licence. Its current version, {current}, may fit better.",
|
||||
"@studysetSheetLicenseNewer": {"placeholders": {"license": {"type": "String"}, "current": {"type": "String"}}},
|
||||
"licenseChooserTitle": "Choose a licence",
|
||||
"licenseChooserSubtitle": "A few questions to find the Creative Commons licence that fits how you want your work shared.",
|
||||
"licenseChooserIntroPrompt": "Do you want to keep any rights to your work?",
|
||||
"licenseChooserIntroYes": "Yes, share it under a licence",
|
||||
"licenseChooserIntroYesDetail": "Others must follow the conditions you choose next.",
|
||||
"licenseChooserIntroNo": "No, dedicate it to the public domain",
|
||||
"licenseChooserIntroNoDetail": "CC0: anyone may use it for anything, without conditions.",
|
||||
"licenseChooserCommercialPrompt": "Allow others to use your work commercially?",
|
||||
"licenseChooserYes": "Yes",
|
||||
"licenseChooserCommercialYesDetail": "Including for profit.",
|
||||
"licenseChooserNo": "No",
|
||||
"licenseChooserCommercialNoDetail": "Non-commercial uses only (NC).",
|
||||
"licenseChooserAdaptationsPrompt": "Allow others to adapt or build upon your work?",
|
||||
"licenseChooserAdaptationsYesDetail": "Remixes and adaptations are allowed.",
|
||||
"licenseChooserAdaptationsShareAlike": "Yes, if they share alike",
|
||||
"licenseChooserAdaptationsShareAlikeDetail": "Adaptations carry this same licence (SA).",
|
||||
"licenseChooserAdaptationsNoDetail": "Sharing only, no adaptations (ND).",
|
||||
"licenseChooserBack": "Back",
|
||||
"licenseChooserRecommended": "Recommended licence",
|
||||
"licenseChooserUse": "Use this licence",
|
||||
"licenseSummaryCc0": "Anyone may use it for anything, without conditions or credit.",
|
||||
"licenseSummaryBy": "Others may share and adapt it, even commercially, as long as they credit you.",
|
||||
"licenseSummaryBySa": "Others may share and adapt it, even commercially, if they credit you and licence their adaptations the same way.",
|
||||
"licenseSummaryByNc": "Others may share and adapt it for non-commercial purposes, with credit to you.",
|
||||
"licenseSummaryByNcSa": "Others may share and adapt it non-commercially, with credit and the same licence on adaptations.",
|
||||
"licenseSummaryByNd": "Others may share it, even commercially, with credit, but may not share adaptations.",
|
||||
"licenseSummaryByNcNd": "Others may share it non-commercially, with credit, but may not adapt it: the strictest Creative Commons licence.",
|
||||
"languageArabic": "Arabic",
|
||||
"languageGerman": "German",
|
||||
"languageGreek": "Greek",
|
||||
|
|
|
|||
|
|
@ -5,5 +5,19 @@
|
|||
"colorPickerLabel": "Color: {color}",
|
||||
"tagSheetColor": "Color",
|
||||
"formatAlignCenter": "Center",
|
||||
"homeCustomise": "Customize"
|
||||
"homeCustomise": "Customize",
|
||||
"studysetSheetLicense": "License",
|
||||
"studysetSheetLicenseLabel": "License",
|
||||
"studysetSheetLicenseHolder": "License holder",
|
||||
"studysetSheetLicenseHolderHelper": "Who to credit: a name or an organization.",
|
||||
"studysetSheetLicenseNewer": "{license} is an older Creative Commons license. Its current version, {current}, may fit better.",
|
||||
"licenseChooserTitle": "Choose a license",
|
||||
"licenseChooserSubtitle": "A few questions to find the Creative Commons license that fits how you want your work shared.",
|
||||
"licenseChooserIntroYes": "Yes, share it under a license",
|
||||
"licenseChooserAdaptationsShareAlikeDetail": "Adaptations carry this same license (SA).",
|
||||
"licenseChooserRecommended": "Recommended license",
|
||||
"licenseChooserUse": "Use this license",
|
||||
"licenseSummaryBySa": "Others may share and adapt it, even commercially, if they credit you and license their adaptations the same way.",
|
||||
"licenseSummaryByNcSa": "Others may share and adapt it non-commercially, with credit and the same license on adaptations.",
|
||||
"licenseSummaryByNcNd": "Others may share it non-commercially, with credit, but may not adapt it: the strictest Creative Commons license."
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1942,6 +1942,228 @@ abstract class AppLocalizations {
|
|||
/// **'Not set'**
|
||||
String get studysetSheetLanguageNotSet;
|
||||
|
||||
/// No description provided for @studysetSheetLicense.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Licence'**
|
||||
String get studysetSheetLicense;
|
||||
|
||||
/// No description provided for @studysetSheetLicenseHelp.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Help me choose'**
|
||||
String get studysetSheetLicenseHelp;
|
||||
|
||||
/// No description provided for @studysetSheetLicenseLabel.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Licence'**
|
||||
String get studysetSheetLicenseLabel;
|
||||
|
||||
/// No description provided for @studysetSheetLicenseHelper.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'The terms others may use the content under, shown below the studyset with a link to them. Examples: CC-BY-4.0, CC-BY-SA-4.0, MIT, All Rights Reserved.'**
|
||||
String get studysetSheetLicenseHelper;
|
||||
|
||||
/// No description provided for @studysetSheetLicenseYear.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Year'**
|
||||
String get studysetSheetLicenseYear;
|
||||
|
||||
/// No description provided for @studysetSheetLicenseYearHelper.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'The copyright year, shown as “© 2019 Name”. Optional.'**
|
||||
String get studysetSheetLicenseYearHelper;
|
||||
|
||||
/// No description provided for @studysetSheetLicenseYearInvalid.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Enter a year between {min} and {max}.'**
|
||||
String studysetSheetLicenseYearInvalid(int min, int max);
|
||||
|
||||
/// No description provided for @studysetSheetLicenseHolder.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Licence holder'**
|
||||
String get studysetSheetLicenseHolder;
|
||||
|
||||
/// No description provided for @studysetSheetLicenseHolderHelper.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Who to credit: a name or an organisation.'**
|
||||
String get studysetSheetLicenseHolderHelper;
|
||||
|
||||
/// No description provided for @studysetSheetLicenseNewer.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'{license} is an older Creative Commons licence. Its current version, {current}, may fit better.'**
|
||||
String studysetSheetLicenseNewer(String license, String current);
|
||||
|
||||
/// No description provided for @licenseChooserTitle.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Choose a licence'**
|
||||
String get licenseChooserTitle;
|
||||
|
||||
/// No description provided for @licenseChooserSubtitle.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'A few questions to find the Creative Commons licence that fits how you want your work shared.'**
|
||||
String get licenseChooserSubtitle;
|
||||
|
||||
/// No description provided for @licenseChooserIntroPrompt.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Do you want to keep any rights to your work?'**
|
||||
String get licenseChooserIntroPrompt;
|
||||
|
||||
/// No description provided for @licenseChooserIntroYes.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Yes, share it under a licence'**
|
||||
String get licenseChooserIntroYes;
|
||||
|
||||
/// No description provided for @licenseChooserIntroYesDetail.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Others must follow the conditions you choose next.'**
|
||||
String get licenseChooserIntroYesDetail;
|
||||
|
||||
/// No description provided for @licenseChooserIntroNo.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'No, dedicate it to the public domain'**
|
||||
String get licenseChooserIntroNo;
|
||||
|
||||
/// No description provided for @licenseChooserIntroNoDetail.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'CC0: anyone may use it for anything, without conditions.'**
|
||||
String get licenseChooserIntroNoDetail;
|
||||
|
||||
/// No description provided for @licenseChooserCommercialPrompt.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Allow others to use your work commercially?'**
|
||||
String get licenseChooserCommercialPrompt;
|
||||
|
||||
/// No description provided for @licenseChooserYes.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Yes'**
|
||||
String get licenseChooserYes;
|
||||
|
||||
/// No description provided for @licenseChooserCommercialYesDetail.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Including for profit.'**
|
||||
String get licenseChooserCommercialYesDetail;
|
||||
|
||||
/// No description provided for @licenseChooserNo.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'No'**
|
||||
String get licenseChooserNo;
|
||||
|
||||
/// No description provided for @licenseChooserCommercialNoDetail.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Non-commercial uses only (NC).'**
|
||||
String get licenseChooserCommercialNoDetail;
|
||||
|
||||
/// No description provided for @licenseChooserAdaptationsPrompt.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Allow others to adapt or build upon your work?'**
|
||||
String get licenseChooserAdaptationsPrompt;
|
||||
|
||||
/// No description provided for @licenseChooserAdaptationsYesDetail.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Remixes and adaptations are allowed.'**
|
||||
String get licenseChooserAdaptationsYesDetail;
|
||||
|
||||
/// No description provided for @licenseChooserAdaptationsShareAlike.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Yes, if they share alike'**
|
||||
String get licenseChooserAdaptationsShareAlike;
|
||||
|
||||
/// No description provided for @licenseChooserAdaptationsShareAlikeDetail.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Adaptations carry this same licence (SA).'**
|
||||
String get licenseChooserAdaptationsShareAlikeDetail;
|
||||
|
||||
/// No description provided for @licenseChooserAdaptationsNoDetail.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Sharing only, no adaptations (ND).'**
|
||||
String get licenseChooserAdaptationsNoDetail;
|
||||
|
||||
/// No description provided for @licenseChooserBack.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Back'**
|
||||
String get licenseChooserBack;
|
||||
|
||||
/// No description provided for @licenseChooserRecommended.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Recommended licence'**
|
||||
String get licenseChooserRecommended;
|
||||
|
||||
/// No description provided for @licenseChooserUse.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Use this licence'**
|
||||
String get licenseChooserUse;
|
||||
|
||||
/// No description provided for @licenseSummaryCc0.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Anyone may use it for anything, without conditions or credit.'**
|
||||
String get licenseSummaryCc0;
|
||||
|
||||
/// No description provided for @licenseSummaryBy.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Others may share and adapt it, even commercially, as long as they credit you.'**
|
||||
String get licenseSummaryBy;
|
||||
|
||||
/// No description provided for @licenseSummaryBySa.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Others may share and adapt it, even commercially, if they credit you and licence their adaptations the same way.'**
|
||||
String get licenseSummaryBySa;
|
||||
|
||||
/// No description provided for @licenseSummaryByNc.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Others may share and adapt it for non-commercial purposes, with credit to you.'**
|
||||
String get licenseSummaryByNc;
|
||||
|
||||
/// No description provided for @licenseSummaryByNcSa.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Others may share and adapt it non-commercially, with credit and the same licence on adaptations.'**
|
||||
String get licenseSummaryByNcSa;
|
||||
|
||||
/// No description provided for @licenseSummaryByNd.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Others may share it, even commercially, with credit, but may not share adaptations.'**
|
||||
String get licenseSummaryByNd;
|
||||
|
||||
/// No description provided for @licenseSummaryByNcNd.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Others may share it non-commercially, with credit, but may not adapt it: the strictest Creative Commons licence.'**
|
||||
String get licenseSummaryByNcNd;
|
||||
|
||||
/// No description provided for @languageArabic.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
|
|
|
|||
|
|
@ -1150,6 +1150,142 @@ class AppLocalizationsDe extends AppLocalizations {
|
|||
@override
|
||||
String get studysetSheetLanguageNotSet => 'Nicht festgelegt';
|
||||
|
||||
@override
|
||||
String get studysetSheetLicense => 'Lizenz';
|
||||
|
||||
@override
|
||||
String get studysetSheetLicenseHelp => 'Hilf mir bei der Wahl';
|
||||
|
||||
@override
|
||||
String get studysetSheetLicenseLabel => 'Lizenz';
|
||||
|
||||
@override
|
||||
String get studysetSheetLicenseHelper =>
|
||||
'Unter welchen Bedingungen andere den Inhalt nutzen dürfen, unter dem Lernset mit einem Link darauf angezeigt. Beispiele: CC-BY-4.0, CC-BY-SA-4.0, MIT, All Rights Reserved.';
|
||||
|
||||
@override
|
||||
String get studysetSheetLicenseYear => 'Jahr';
|
||||
|
||||
@override
|
||||
String get studysetSheetLicenseYearHelper =>
|
||||
'Das Jahr des Urheberrechts, angezeigt als „© 2019 Name“. Optional.';
|
||||
|
||||
@override
|
||||
String studysetSheetLicenseYearInvalid(int min, int max) {
|
||||
return 'Gib ein Jahr zwischen $min und $max ein.';
|
||||
}
|
||||
|
||||
@override
|
||||
String get studysetSheetLicenseHolder => 'Rechteinhaber';
|
||||
|
||||
@override
|
||||
String get studysetSheetLicenseHolderHelper =>
|
||||
'Wer genannt wird: ein Name oder eine Organisation.';
|
||||
|
||||
@override
|
||||
String studysetSheetLicenseNewer(String license, String current) {
|
||||
return '$license ist eine ältere Creative-Commons-Lizenz. Ihre aktuelle Version, $current, passt vielleicht besser.';
|
||||
}
|
||||
|
||||
@override
|
||||
String get licenseChooserTitle => 'Lizenz wählen';
|
||||
|
||||
@override
|
||||
String get licenseChooserSubtitle =>
|
||||
'Ein paar Fragen, um die Creative-Commons-Lizenz zu finden, die dazu passt, wie du dein Werk teilen möchtest.';
|
||||
|
||||
@override
|
||||
String get licenseChooserIntroPrompt =>
|
||||
'Möchtest du Rechte an deinem Werk behalten?';
|
||||
|
||||
@override
|
||||
String get licenseChooserIntroYes => 'Ja, unter einer Lizenz teilen';
|
||||
|
||||
@override
|
||||
String get licenseChooserIntroYesDetail =>
|
||||
'Andere müssen die Bedingungen einhalten, die du als Nächstes wählst.';
|
||||
|
||||
@override
|
||||
String get licenseChooserIntroNo => 'Nein, als Gemeingut freigeben';
|
||||
|
||||
@override
|
||||
String get licenseChooserIntroNoDetail =>
|
||||
'CC0: Jeder darf es für alles nutzen, ohne Bedingungen.';
|
||||
|
||||
@override
|
||||
String get licenseChooserCommercialPrompt =>
|
||||
'Dürfen andere dein Werk kommerziell nutzen?';
|
||||
|
||||
@override
|
||||
String get licenseChooserYes => 'Ja';
|
||||
|
||||
@override
|
||||
String get licenseChooserCommercialYesDetail => 'Auch mit Gewinnabsicht.';
|
||||
|
||||
@override
|
||||
String get licenseChooserNo => 'Nein';
|
||||
|
||||
@override
|
||||
String get licenseChooserCommercialNoDetail =>
|
||||
'Nur nicht-kommerzielle Nutzung (NC).';
|
||||
|
||||
@override
|
||||
String get licenseChooserAdaptationsPrompt =>
|
||||
'Dürfen andere dein Werk bearbeiten oder darauf aufbauen?';
|
||||
|
||||
@override
|
||||
String get licenseChooserAdaptationsYesDetail =>
|
||||
'Bearbeitungen und Remixe sind erlaubt.';
|
||||
|
||||
@override
|
||||
String get licenseChooserAdaptationsShareAlike =>
|
||||
'Ja, unter gleichen Bedingungen';
|
||||
|
||||
@override
|
||||
String get licenseChooserAdaptationsShareAlikeDetail =>
|
||||
'Bearbeitungen stehen unter derselben Lizenz (SA).';
|
||||
|
||||
@override
|
||||
String get licenseChooserAdaptationsNoDetail =>
|
||||
'Nur Weitergabe, keine Bearbeitungen (ND).';
|
||||
|
||||
@override
|
||||
String get licenseChooserBack => 'Zurück';
|
||||
|
||||
@override
|
||||
String get licenseChooserRecommended => 'Empfohlene Lizenz';
|
||||
|
||||
@override
|
||||
String get licenseChooserUse => 'Diese Lizenz verwenden';
|
||||
|
||||
@override
|
||||
String get licenseSummaryCc0 =>
|
||||
'Jeder darf es für alles nutzen, ohne Bedingungen und ohne Nennung.';
|
||||
|
||||
@override
|
||||
String get licenseSummaryBy =>
|
||||
'Andere dürfen es teilen und bearbeiten, auch kommerziell, solange sie dich nennen.';
|
||||
|
||||
@override
|
||||
String get licenseSummaryBySa =>
|
||||
'Andere dürfen es teilen und bearbeiten, auch kommerziell, wenn sie dich nennen und ihre Bearbeitungen genauso lizenzieren.';
|
||||
|
||||
@override
|
||||
String get licenseSummaryByNc =>
|
||||
'Andere dürfen es für nicht-kommerzielle Zwecke teilen und bearbeiten, mit Nennung.';
|
||||
|
||||
@override
|
||||
String get licenseSummaryByNcSa =>
|
||||
'Andere dürfen es nicht-kommerziell teilen und bearbeiten, mit Nennung und derselben Lizenz für Bearbeitungen.';
|
||||
|
||||
@override
|
||||
String get licenseSummaryByNd =>
|
||||
'Andere dürfen es teilen, auch kommerziell, mit Nennung, aber keine Bearbeitungen weitergeben.';
|
||||
|
||||
@override
|
||||
String get licenseSummaryByNcNd =>
|
||||
'Andere dürfen es nicht-kommerziell teilen, mit Nennung, aber nicht bearbeiten: die strengste Creative-Commons-Lizenz.';
|
||||
|
||||
@override
|
||||
String get languageArabic => 'Arabisch';
|
||||
|
||||
|
|
|
|||
|
|
@ -1144,6 +1144,141 @@ class AppLocalizationsEn extends AppLocalizations {
|
|||
@override
|
||||
String get studysetSheetLanguageNotSet => 'Not set';
|
||||
|
||||
@override
|
||||
String get studysetSheetLicense => 'Licence';
|
||||
|
||||
@override
|
||||
String get studysetSheetLicenseHelp => 'Help me choose';
|
||||
|
||||
@override
|
||||
String get studysetSheetLicenseLabel => 'Licence';
|
||||
|
||||
@override
|
||||
String get studysetSheetLicenseHelper =>
|
||||
'The terms others may use the content under, shown below the studyset with a link to them. Examples: CC-BY-4.0, CC-BY-SA-4.0, MIT, All Rights Reserved.';
|
||||
|
||||
@override
|
||||
String get studysetSheetLicenseYear => 'Year';
|
||||
|
||||
@override
|
||||
String get studysetSheetLicenseYearHelper =>
|
||||
'The copyright year, shown as “© 2019 Name”. Optional.';
|
||||
|
||||
@override
|
||||
String studysetSheetLicenseYearInvalid(int min, int max) {
|
||||
return 'Enter a year between $min and $max.';
|
||||
}
|
||||
|
||||
@override
|
||||
String get studysetSheetLicenseHolder => 'Licence holder';
|
||||
|
||||
@override
|
||||
String get studysetSheetLicenseHolderHelper =>
|
||||
'Who to credit: a name or an organisation.';
|
||||
|
||||
@override
|
||||
String studysetSheetLicenseNewer(String license, String current) {
|
||||
return '$license is an older Creative Commons licence. Its current version, $current, may fit better.';
|
||||
}
|
||||
|
||||
@override
|
||||
String get licenseChooserTitle => 'Choose a licence';
|
||||
|
||||
@override
|
||||
String get licenseChooserSubtitle =>
|
||||
'A few questions to find the Creative Commons licence that fits how you want your work shared.';
|
||||
|
||||
@override
|
||||
String get licenseChooserIntroPrompt =>
|
||||
'Do you want to keep any rights to your work?';
|
||||
|
||||
@override
|
||||
String get licenseChooserIntroYes => 'Yes, share it under a licence';
|
||||
|
||||
@override
|
||||
String get licenseChooserIntroYesDetail =>
|
||||
'Others must follow the conditions you choose next.';
|
||||
|
||||
@override
|
||||
String get licenseChooserIntroNo => 'No, dedicate it to the public domain';
|
||||
|
||||
@override
|
||||
String get licenseChooserIntroNoDetail =>
|
||||
'CC0: anyone may use it for anything, without conditions.';
|
||||
|
||||
@override
|
||||
String get licenseChooserCommercialPrompt =>
|
||||
'Allow others to use your work commercially?';
|
||||
|
||||
@override
|
||||
String get licenseChooserYes => 'Yes';
|
||||
|
||||
@override
|
||||
String get licenseChooserCommercialYesDetail => 'Including for profit.';
|
||||
|
||||
@override
|
||||
String get licenseChooserNo => 'No';
|
||||
|
||||
@override
|
||||
String get licenseChooserCommercialNoDetail =>
|
||||
'Non-commercial uses only (NC).';
|
||||
|
||||
@override
|
||||
String get licenseChooserAdaptationsPrompt =>
|
||||
'Allow others to adapt or build upon your work?';
|
||||
|
||||
@override
|
||||
String get licenseChooserAdaptationsYesDetail =>
|
||||
'Remixes and adaptations are allowed.';
|
||||
|
||||
@override
|
||||
String get licenseChooserAdaptationsShareAlike => 'Yes, if they share alike';
|
||||
|
||||
@override
|
||||
String get licenseChooserAdaptationsShareAlikeDetail =>
|
||||
'Adaptations carry this same licence (SA).';
|
||||
|
||||
@override
|
||||
String get licenseChooserAdaptationsNoDetail =>
|
||||
'Sharing only, no adaptations (ND).';
|
||||
|
||||
@override
|
||||
String get licenseChooserBack => 'Back';
|
||||
|
||||
@override
|
||||
String get licenseChooserRecommended => 'Recommended licence';
|
||||
|
||||
@override
|
||||
String get licenseChooserUse => 'Use this licence';
|
||||
|
||||
@override
|
||||
String get licenseSummaryCc0 =>
|
||||
'Anyone may use it for anything, without conditions or credit.';
|
||||
|
||||
@override
|
||||
String get licenseSummaryBy =>
|
||||
'Others may share and adapt it, even commercially, as long as they credit you.';
|
||||
|
||||
@override
|
||||
String get licenseSummaryBySa =>
|
||||
'Others may share and adapt it, even commercially, if they credit you and licence their adaptations the same way.';
|
||||
|
||||
@override
|
||||
String get licenseSummaryByNc =>
|
||||
'Others may share and adapt it for non-commercial purposes, with credit to you.';
|
||||
|
||||
@override
|
||||
String get licenseSummaryByNcSa =>
|
||||
'Others may share and adapt it non-commercially, with credit and the same licence on adaptations.';
|
||||
|
||||
@override
|
||||
String get licenseSummaryByNd =>
|
||||
'Others may share it, even commercially, with credit, but may not share adaptations.';
|
||||
|
||||
@override
|
||||
String get licenseSummaryByNcNd =>
|
||||
'Others may share it non-commercially, with credit, but may not adapt it: the strictest Creative Commons licence.';
|
||||
|
||||
@override
|
||||
String get languageArabic => 'Arabic';
|
||||
|
||||
|
|
@ -4019,6 +4154,56 @@ class AppLocalizationsEnUs extends AppLocalizationsEn {
|
|||
return 'Color: $color';
|
||||
}
|
||||
|
||||
@override
|
||||
String get studysetSheetLicense => 'License';
|
||||
|
||||
@override
|
||||
String get studysetSheetLicenseLabel => 'License';
|
||||
|
||||
@override
|
||||
String get studysetSheetLicenseHolder => 'License holder';
|
||||
|
||||
@override
|
||||
String get studysetSheetLicenseHolderHelper =>
|
||||
'Who to credit: a name or an organization.';
|
||||
|
||||
@override
|
||||
String studysetSheetLicenseNewer(String license, String current) {
|
||||
return '$license is an older Creative Commons license. Its current version, $current, may fit better.';
|
||||
}
|
||||
|
||||
@override
|
||||
String get licenseChooserTitle => 'Choose a license';
|
||||
|
||||
@override
|
||||
String get licenseChooserSubtitle =>
|
||||
'A few questions to find the Creative Commons license that fits how you want your work shared.';
|
||||
|
||||
@override
|
||||
String get licenseChooserIntroYes => 'Yes, share it under a license';
|
||||
|
||||
@override
|
||||
String get licenseChooserAdaptationsShareAlikeDetail =>
|
||||
'Adaptations carry this same license (SA).';
|
||||
|
||||
@override
|
||||
String get licenseChooserRecommended => 'Recommended license';
|
||||
|
||||
@override
|
||||
String get licenseChooserUse => 'Use this license';
|
||||
|
||||
@override
|
||||
String get licenseSummaryBySa =>
|
||||
'Others may share and adapt it, even commercially, if they credit you and license their adaptations the same way.';
|
||||
|
||||
@override
|
||||
String get licenseSummaryByNcSa =>
|
||||
'Others may share and adapt it non-commercially, with credit and the same license on adaptations.';
|
||||
|
||||
@override
|
||||
String get licenseSummaryByNcNd =>
|
||||
'Others may share it non-commercially, with credit, but may not adapt it: the strictest Creative Commons license.';
|
||||
|
||||
@override
|
||||
String get tagSheetColor => 'Color';
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,89 @@
|
|||
/// Licences content commonly comes under, as a studyset keeps them: SPDX
|
||||
/// identifiers, and "All Rights Reserved", which has none but marks content
|
||||
/// not licensed to anyone. They are the licence field's suggestions; any
|
||||
/// other licence is kept as typed.
|
||||
const knownLicenses = [
|
||||
'CC0-1.0',
|
||||
'CC-BY-4.0',
|
||||
'CC-BY-SA-4.0',
|
||||
'CC-BY-NC-4.0',
|
||||
'CC-BY-NC-SA-4.0',
|
||||
'CC-BY-ND-4.0',
|
||||
'CC-BY-NC-ND-4.0',
|
||||
'MIT',
|
||||
'Apache-2.0',
|
||||
'BSD-3-Clause',
|
||||
'GPL-3.0-or-later',
|
||||
'AGPL-3.0-or-later',
|
||||
'All Rights Reserved',
|
||||
];
|
||||
|
||||
/// Short names and spellings that don't become a known licence by folding
|
||||
/// alone ([_fold]): a Creative Commons licence without a version means the
|
||||
/// current one, a GPL without "or later" the usual "or later".
|
||||
const _aliases = {
|
||||
'CC0': 'CC0-1.0',
|
||||
'PUBLIC-DOMAIN': 'CC0-1.0',
|
||||
'CC-BY': 'CC-BY-4.0',
|
||||
'CC-BY-SA': 'CC-BY-SA-4.0',
|
||||
'CC-BY-NC': 'CC-BY-NC-4.0',
|
||||
'CC-BY-NC-SA': 'CC-BY-NC-SA-4.0',
|
||||
'CC-BY-ND': 'CC-BY-ND-4.0',
|
||||
'CC-BY-NC-ND': 'CC-BY-NC-ND-4.0',
|
||||
'APACHE-2': 'Apache-2.0',
|
||||
'GPL-3.0': 'GPL-3.0-or-later',
|
||||
'GPLV3': 'GPL-3.0-or-later',
|
||||
'AGPL-3.0': 'AGPL-3.0-or-later',
|
||||
'AGPLV3': 'AGPL-3.0-or-later',
|
||||
'ARR': 'All Rights Reserved',
|
||||
};
|
||||
|
||||
final _byFolded = {
|
||||
for (final license in knownLicenses) _fold(license): license,
|
||||
..._aliases,
|
||||
};
|
||||
|
||||
/// [raw] in capitals, every run of spaces, underscores and hyphens one
|
||||
/// hyphen: `cc by_sa 4.0` and `CC-BY-SA-4.0` fold alike.
|
||||
String _fold(String raw) => raw
|
||||
.trim()
|
||||
.toUpperCase()
|
||||
.replaceAll(RegExp(r'[\s_-]+'), '-')
|
||||
.replaceAll(RegExp(r'^-+|-+$'), '');
|
||||
|
||||
/// A Creative Commons licence, folded: its kind, version and jurisdiction.
|
||||
final _creativeCommons = RegExp(
|
||||
r'^CC-(BY(?:-NC)?(?:-SA|-ND)?)-(\d\.\d)(?:-([A-Z]{2,}))?$',
|
||||
);
|
||||
|
||||
/// [raw] as a studyset keeps it: a known licence in its own spelling
|
||||
/// (`Cc By 4.0` is `CC-BY-4.0`), any Creative Commons licence by its SPDX
|
||||
/// identifier (`cc by-nc-sa 3.0 us` is `CC-BY-NC-SA-3.0-US`), and anything
|
||||
/// else as typed, spaces tidied; null for nothing.
|
||||
String? canonicalLicense(String? raw) {
|
||||
final tidy = raw?.trim().replaceAll(RegExp(r'\s+'), ' ') ?? '';
|
||||
if (tidy.isEmpty) {
|
||||
return null;
|
||||
}
|
||||
final folded = _fold(tidy);
|
||||
return _byFolded[folded] ??
|
||||
(_creativeCommons.hasMatch(folded) ? folded : tidy);
|
||||
}
|
||||
|
||||
/// The current version of the Creative Commons licence [license] when it
|
||||
/// is an older one or one ported to a country (`CC-BY-NC-SA-4.0` for
|
||||
/// `CC-BY-NC-SA-3.0-US`); null when it is current, or not Creative Commons.
|
||||
String? newerCreativeCommons(String license) {
|
||||
final match = _creativeCommons.firstMatch(license);
|
||||
if (match == null || (match[2] == '4.0' && match[3] == null)) {
|
||||
return null;
|
||||
}
|
||||
return 'CC-${match[1]}-4.0';
|
||||
}
|
||||
|
||||
/// The earliest copyright year the licence's year takes.
|
||||
const earliestLicenseYear = 1000;
|
||||
|
||||
/// A studyset's licence as a whole: the licence, who holds the rights, and
|
||||
/// the copyright year; each null when not given.
|
||||
typedef StudysetLicense = ({String? license, String? holder, int? year});
|
||||
|
|
@ -0,0 +1,305 @@
|
|||
import 'package:halkyon_learn_flutter/l10n/app_localizations.dart';
|
||||
import 'package:halkyon_learn_flutter/src/core/widget/adaptive_sheet.dart';
|
||||
import 'package:halkyon_learn_flutter/src/feature/studyset/model/license_link.dart';
|
||||
import 'package:lucide_icons_flutter/lucide_icons.dart';
|
||||
import 'package:material_ui/material_ui.dart';
|
||||
import 'package:url_launcher/url_launcher.dart';
|
||||
|
||||
/// Finds the Creative Commons licence that fits how someone wants their
|
||||
/// work shared, through a few plain questions, as Creative Commons' own
|
||||
/// chooser does: whether to keep any rights, whether others may use it
|
||||
/// commercially, and whether they may adapt it. Completes with the licence
|
||||
/// recommended, one of the six current CC licences or the public-domain
|
||||
/// dedication CC0 (`CC-BY-NC-SA-4.0`), once taken, or with null.
|
||||
/// Licences for code, or of one's own, are typed in the licence field.
|
||||
Future<String?> showLicenseChooser(BuildContext context) =>
|
||||
showAdaptiveSheet<String>(
|
||||
context,
|
||||
builder: (context) => const _LicenseChooser(),
|
||||
);
|
||||
|
||||
/// The chooser's questions, then what they come to.
|
||||
enum _Step() {
|
||||
rights,
|
||||
commercial,
|
||||
adaptations,
|
||||
result,
|
||||
}
|
||||
|
||||
/// Whether others may adapt the work: freely, if they share alike, or not.
|
||||
enum _Adaptations() {
|
||||
yes,
|
||||
shareAlike,
|
||||
no,
|
||||
}
|
||||
|
||||
class const _LicenseChooser() extends StatefulWidget {
|
||||
@override
|
||||
State<_LicenseChooser> createState() => _LicenseChooserState();
|
||||
}
|
||||
|
||||
class _LicenseChooserState() extends State<_LicenseChooser> {
|
||||
var _step = _Step.rights;
|
||||
var _publicDomain = false;
|
||||
var _commercial = true;
|
||||
var _adaptations = _Adaptations.yes;
|
||||
|
||||
/// The licence the answers so far come to.
|
||||
String get _license => _publicDomain
|
||||
? 'CC0-1.0'
|
||||
: [
|
||||
'CC-BY',
|
||||
if (!_commercial) 'NC',
|
||||
if (_adaptations == _Adaptations.shareAlike) 'SA',
|
||||
if (_adaptations == _Adaptations.no) 'ND',
|
||||
'4.0',
|
||||
].join('-');
|
||||
|
||||
void _answer(VoidCallback answer, _Step next) => setState(() {
|
||||
answer();
|
||||
_step = next;
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final l10n = AppLocalizations.of(context);
|
||||
final theme = Theme.of(context);
|
||||
return SheetScaffold(
|
||||
title: l10n.licenseChooserTitle,
|
||||
body: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
children: [
|
||||
const SizedBox(height: 4),
|
||||
Text(
|
||||
l10n.licenseChooserSubtitle,
|
||||
style: theme.textTheme.bodySmall?.copyWith(
|
||||
color: theme.colorScheme.onSurfaceVariant,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 20),
|
||||
// One step swapped for the next.
|
||||
AnimatedSwitcher(
|
||||
duration: const Duration(milliseconds: 150),
|
||||
child: KeyedSubtree(key: ValueKey(_step), child: _stepView(l10n)),
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _stepView(AppLocalizations l10n) => switch (_step) {
|
||||
_Step.rights => _Question(
|
||||
prompt: l10n.licenseChooserIntroPrompt,
|
||||
options: [
|
||||
_Option(
|
||||
label: l10n.licenseChooserIntroYes,
|
||||
detail: l10n.licenseChooserIntroYesDetail,
|
||||
onTap: () => _answer(() => _publicDomain = false, _Step.commercial),
|
||||
),
|
||||
_Option(
|
||||
label: l10n.licenseChooserIntroNo,
|
||||
detail: l10n.licenseChooserIntroNoDetail,
|
||||
onTap: () => _answer(() => _publicDomain = true, _Step.result),
|
||||
),
|
||||
],
|
||||
),
|
||||
_Step.commercial => _Question(
|
||||
prompt: l10n.licenseChooserCommercialPrompt,
|
||||
onBack: () => setState(() => _step = _Step.rights),
|
||||
options: [
|
||||
_Option(
|
||||
label: l10n.licenseChooserYes,
|
||||
detail: l10n.licenseChooserCommercialYesDetail,
|
||||
onTap: () => _answer(() => _commercial = true, _Step.adaptations),
|
||||
),
|
||||
_Option(
|
||||
label: l10n.licenseChooserNo,
|
||||
detail: l10n.licenseChooserCommercialNoDetail,
|
||||
onTap: () => _answer(() => _commercial = false, _Step.adaptations),
|
||||
),
|
||||
],
|
||||
),
|
||||
_Step.adaptations => _Question(
|
||||
prompt: l10n.licenseChooserAdaptationsPrompt,
|
||||
onBack: () => setState(() => _step = _Step.commercial),
|
||||
options: [
|
||||
for (final (adaptations, label, detail) in [
|
||||
(
|
||||
_Adaptations.yes,
|
||||
l10n.licenseChooserYes,
|
||||
l10n.licenseChooserAdaptationsYesDetail,
|
||||
),
|
||||
(
|
||||
_Adaptations.shareAlike,
|
||||
l10n.licenseChooserAdaptationsShareAlike,
|
||||
l10n.licenseChooserAdaptationsShareAlikeDetail,
|
||||
),
|
||||
(
|
||||
_Adaptations.no,
|
||||
l10n.licenseChooserNo,
|
||||
l10n.licenseChooserAdaptationsNoDetail,
|
||||
),
|
||||
])
|
||||
_Option(
|
||||
label: label,
|
||||
detail: detail,
|
||||
onTap: () =>
|
||||
_answer(() => _adaptations = adaptations, _Step.result),
|
||||
),
|
||||
],
|
||||
),
|
||||
_Step.result => _Result(
|
||||
license: _license,
|
||||
summary: _summaryOf(l10n, _license),
|
||||
onBack: () => setState(
|
||||
() => _step = _publicDomain ? _Step.rights : _Step.adaptations,
|
||||
),
|
||||
onUse: () => Navigator.of(context).pop(_license),
|
||||
),
|
||||
};
|
||||
}
|
||||
|
||||
/// What the licence [license], one the chooser recommends, lets others do.
|
||||
String _summaryOf(AppLocalizations l10n, String license) => switch (license) {
|
||||
'CC0-1.0' => l10n.licenseSummaryCc0,
|
||||
'CC-BY-4.0' => l10n.licenseSummaryBy,
|
||||
'CC-BY-SA-4.0' => l10n.licenseSummaryBySa,
|
||||
'CC-BY-NC-4.0' => l10n.licenseSummaryByNc,
|
||||
'CC-BY-NC-SA-4.0' => l10n.licenseSummaryByNcSa,
|
||||
'CC-BY-ND-4.0' => l10n.licenseSummaryByNd,
|
||||
_ => l10n.licenseSummaryByNcNd,
|
||||
};
|
||||
|
||||
/// A question with its answers, each a card to tap, and Back below them
|
||||
/// from the second question on.
|
||||
class const _Question({
|
||||
required final String prompt,
|
||||
required final List<_Option> options,
|
||||
final VoidCallback? onBack,
|
||||
}) extends StatelessWidget {
|
||||
@override
|
||||
Widget build(BuildContext context) => Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
children: [
|
||||
Text(prompt, style: Theme.of(context).textTheme.titleMedium),
|
||||
const SizedBox(height: 12),
|
||||
for (final option in options) ...[option, const SizedBox(height: 8)],
|
||||
if (onBack != null)
|
||||
Align(
|
||||
alignment: AlignmentDirectional.centerStart,
|
||||
child: _BackButton(onPressed: onBack!),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
class const _BackButton({required final VoidCallback onPressed})
|
||||
extends StatelessWidget {
|
||||
@override
|
||||
Widget build(BuildContext context) => TextButton.icon(
|
||||
onPressed: onPressed,
|
||||
icon: const Icon(LucideIcons.arrowLeft, size: 18),
|
||||
label: Text(AppLocalizations.of(context).licenseChooserBack),
|
||||
);
|
||||
}
|
||||
|
||||
/// An answer: what it is, and what it means.
|
||||
class const _Option({
|
||||
required final String label,
|
||||
required final String detail,
|
||||
required final VoidCallback onTap,
|
||||
}) extends StatelessWidget {
|
||||
@override
|
||||
Widget build(BuildContext context) => Card(
|
||||
margin: EdgeInsets.zero,
|
||||
elevation: 0,
|
||||
// A choice within the sheet, not a raised card.
|
||||
color: Theme.of(context).colorScheme.surfaceContainerHighest,
|
||||
clipBehavior: Clip.antiAlias,
|
||||
child: ListTile(
|
||||
title: Text(label),
|
||||
subtitle: Text(detail),
|
||||
trailing: const Icon(LucideIcons.chevronRight),
|
||||
onTap: onTap,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
/// The licence recommended: its identifier, what it lets others do, and a
|
||||
/// link to its full terms; Back, and taking it.
|
||||
class const _Result({
|
||||
required final String license,
|
||||
required final String summary,
|
||||
required final VoidCallback onBack,
|
||||
required final VoidCallback onUse,
|
||||
}) extends StatelessWidget {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final l10n = AppLocalizations.of(context);
|
||||
final theme = Theme.of(context);
|
||||
final terms = licenseUrl(license);
|
||||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
children: [
|
||||
Text(
|
||||
l10n.licenseChooserRecommended,
|
||||
style: theme.textTheme.titleMedium,
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
DecoratedBox(
|
||||
decoration: BoxDecoration(
|
||||
color: theme.colorScheme.surfaceContainerHighest,
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
),
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(16),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(license, style: theme.textTheme.titleMedium),
|
||||
const SizedBox(height: 6),
|
||||
Text(summary, style: theme.textTheme.bodyMedium),
|
||||
if (terms != null) ...[
|
||||
const SizedBox(height: 8),
|
||||
InkWell(
|
||||
onTap: () => launchUrl(terms),
|
||||
borderRadius: BorderRadius.circular(4),
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Flexible(
|
||||
child: Text(
|
||||
'$terms',
|
||||
style: theme.textTheme.bodySmall?.copyWith(
|
||||
color: theme.colorScheme.primary,
|
||||
decoration: TextDecoration.underline,
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 4),
|
||||
Icon(
|
||||
LucideIcons.externalLink,
|
||||
size: 14,
|
||||
color: theme.colorScheme.primary,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
Row(
|
||||
children: [
|
||||
_BackButton(onPressed: onBack),
|
||||
const Spacer(),
|
||||
FilledButton(onPressed: onUse, child: Text(l10n.licenseChooserUse)),
|
||||
],
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -56,11 +56,17 @@ class _StudysetRailState() extends State<StudysetRail> {
|
|||
|
||||
Future<void> _create() async {
|
||||
final bloc = context.read<StudysetListBloc>();
|
||||
final parent = widget.parent.content;
|
||||
final content = await showStudysetSheet(
|
||||
context,
|
||||
insideAnother: true,
|
||||
inheritedColorName: colorNameOf(widget.parent.id, widget.all),
|
||||
inheritedLanguages: languagesOf(widget.parent.id, widget.all),
|
||||
inheritedLicense: (
|
||||
license: parent.license,
|
||||
holder: parent.licenseHolder,
|
||||
year: parent.licenseYear,
|
||||
),
|
||||
);
|
||||
if (content != null) {
|
||||
bloc.add(StudysetListEventCreateRequested(content));
|
||||
|
|
|
|||
|
|
@ -8,27 +8,32 @@ import 'package:halkyon_learn_flutter/src/core/widget/adaptive_sheet.dart';
|
|||
import 'package:halkyon_learn_flutter/src/core/widget/confirm_dialog.dart';
|
||||
import 'package:halkyon_learn_flutter/src/core/widget/field_label.dart';
|
||||
import 'package:halkyon_learn_flutter/src/core/widget/palette_color_picker.dart';
|
||||
import 'package:halkyon_learn_flutter/src/feature/studyset/model/license.dart';
|
||||
import 'package:halkyon_learn_flutter/src/feature/studyset/model/studyset_content.dart';
|
||||
import 'package:halkyon_learn_flutter/src/feature/studyset/model/studyset_tree.dart';
|
||||
import 'package:halkyon_learn_flutter/src/feature/studyset/widget/license_chooser_sheet.dart';
|
||||
import 'package:lucide_icons_flutter/lucide_icons.dart';
|
||||
import 'package:material_ui/material_ui.dart';
|
||||
|
||||
/// Creates a studyset's content, or edits [initial]: its title, secondary
|
||||
/// title, description, colour and languages. Completes with the new content
|
||||
/// once the person saves, or with null when they close the sheet; closing it
|
||||
/// with changes asks first.
|
||||
/// title, description, colour, licence and languages. Completes with the new
|
||||
/// content once the person saves, or with null when they close the sheet;
|
||||
/// closing it with changes asks first.
|
||||
///
|
||||
/// A studyset [insideAnother] can take the colour of the studyset it is in
|
||||
/// ([inheritedColorName], shown meanwhile), and a new one does at first; at
|
||||
/// the top level a new studyset is cobalt. Its languages are off until set,
|
||||
/// taking those of the studyset it is in ([inheritedLanguages], which they
|
||||
/// start from once set). What the sheet doesn't show keeps its value.
|
||||
/// start from once set). A new studyset starts with the licence of the one
|
||||
/// it is in, [inheritedLicense], its own to change from then on. What the
|
||||
/// sheet doesn't show keeps its value.
|
||||
Future<StudysetContent?> showStudysetSheet(
|
||||
BuildContext context, {
|
||||
StudysetContent? initial,
|
||||
bool insideAnother = false,
|
||||
String? inheritedColorName,
|
||||
StudysetLanguages inheritedLanguages = (front: null, back: null),
|
||||
StudysetLicense inheritedLicense = (license: null, holder: null, year: null),
|
||||
}) => showAdaptiveSheet<StudysetContent>(
|
||||
context,
|
||||
// Swiping would discard changes without asking.
|
||||
|
|
@ -38,6 +43,7 @@ Future<StudysetContent?> showStudysetSheet(
|
|||
insideAnother: insideAnother,
|
||||
inheritedColorName: inheritedColorName,
|
||||
inheritedLanguages: inheritedLanguages,
|
||||
inheritedLicense: inheritedLicense,
|
||||
),
|
||||
);
|
||||
|
||||
|
|
@ -49,6 +55,7 @@ class const _StudysetSheet({
|
|||
required final bool insideAnother,
|
||||
required final String? inheritedColorName,
|
||||
required final StudysetLanguages inheritedLanguages,
|
||||
required final StudysetLicense inheritedLicense,
|
||||
}) extends StatefulWidget {
|
||||
@override
|
||||
State<_StudysetSheet> createState() => _StudysetSheetState();
|
||||
|
|
@ -61,6 +68,25 @@ class _StudysetSheetState() extends State<_StudysetSheet> {
|
|||
);
|
||||
late var _description = widget.initial?.description ?? RichContent.empty();
|
||||
|
||||
/// The licence as it was: the studyset's, or for a new one the one it is
|
||||
/// in, which it starts with.
|
||||
late final StudysetLicense _licenseBefore = switch (widget.initial) {
|
||||
final initial? => (
|
||||
license: initial.license,
|
||||
holder: initial.licenseHolder,
|
||||
year: initial.licenseYear,
|
||||
),
|
||||
null => widget.inheritedLicense,
|
||||
};
|
||||
late final _license = TextEditingController(text: _licenseBefore.license);
|
||||
late final _licenseHolder = TextEditingController(
|
||||
text: _licenseBefore.holder,
|
||||
);
|
||||
late final _licenseYear = TextEditingController(
|
||||
text: _licenseBefore.year?.toString(),
|
||||
);
|
||||
final _licenseFocus = FocusNode();
|
||||
|
||||
/// The colour's name as the content holds it: none to take the colour of
|
||||
/// the studyset it is in (or, at the top level, the neutral one of
|
||||
/// studysets from before colours). An existing studyset keeps its colour,
|
||||
|
|
@ -86,27 +112,63 @@ class _StudysetSheetState() extends State<_StudysetSheet> {
|
|||
// The Create or Save button follows the title.
|
||||
_title.addListener(() => setState(() {}));
|
||||
_secondaryTitle.addListener(() => setState(() {}));
|
||||
// As are the note on an older licence and the year's error.
|
||||
_license.addListener(() => setState(() {}));
|
||||
_licenseHolder.addListener(() => setState(() {}));
|
||||
_licenseYear.addListener(() => setState(() {}));
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_title.dispose();
|
||||
_secondaryTitle.dispose();
|
||||
_license.dispose();
|
||||
_licenseHolder.dispose();
|
||||
_licenseYear.dispose();
|
||||
_licenseFocus.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
/// The licence as the sheet holds it.
|
||||
StudysetLicense get _licenseNow {
|
||||
final holder = _licenseHolder.text.trim();
|
||||
return (
|
||||
license: canonicalLicense(_license.text),
|
||||
holder: holder.isEmpty ? null : holder,
|
||||
year: int.tryParse(_licenseYear.text.trim()),
|
||||
);
|
||||
}
|
||||
|
||||
/// What is wrong with the year: outside the years a copyright can have
|
||||
/// been from, as far as next year; null when it is fine or empty.
|
||||
String? _yearError(AppLocalizations l10n) {
|
||||
final year = int.tryParse(_licenseYear.text.trim());
|
||||
final latest = DateTime.now().year + 1;
|
||||
if (year == null || (year >= earliestLicenseYear && year <= latest)) {
|
||||
return null;
|
||||
}
|
||||
return l10n.studysetSheetLicenseYearInvalid(earliestLicenseYear, latest);
|
||||
}
|
||||
|
||||
Future<void> _chooseLicense() async {
|
||||
if (await showLicenseChooser(context) case final license?) {
|
||||
_license.text = license;
|
||||
}
|
||||
}
|
||||
|
||||
/// The content as the sheet holds it.
|
||||
StudysetContent get _content {
|
||||
final initial = widget.initial;
|
||||
final secondaryTitle = _secondaryTitle.text.trim();
|
||||
final license = _licenseNow;
|
||||
return StudysetContent(
|
||||
title: _title.text.trim(),
|
||||
description: _description,
|
||||
secondaryTitle: secondaryTitle.isEmpty ? null : secondaryTitle,
|
||||
colorName: _colorName,
|
||||
license: initial?.license,
|
||||
licenseHolder: initial?.licenseHolder,
|
||||
licenseYear: initial?.licenseYear,
|
||||
license: license.license,
|
||||
licenseHolder: license.holder,
|
||||
licenseYear: license.year,
|
||||
frontLanguage: _setLanguages ? _frontLanguage : null,
|
||||
backLanguage: _setLanguages ? _backLanguage : null,
|
||||
studyOptions: initial?.studyOptions,
|
||||
|
|
@ -121,6 +183,7 @@ class _StudysetSheetState() extends State<_StudysetSheet> {
|
|||
_secondaryTitle.text.trim().isNotEmpty ||
|
||||
_description != RichContent.empty() ||
|
||||
_colorName != (widget.insideAnother ? null : _defaultColor.name) ||
|
||||
_licenseNow != _licenseBefore ||
|
||||
(_setLanguages && (_frontLanguage ?? _backLanguage) != null),
|
||||
};
|
||||
|
||||
|
|
@ -171,7 +234,7 @@ class _StudysetSheetState() extends State<_StudysetSheet> {
|
|||
// A long form whose parts shouldn't make it jump.
|
||||
heightFactor: 0.85,
|
||||
footer: FilledButton(
|
||||
onPressed: _title.text.trim().isEmpty
|
||||
onPressed: _title.text.trim().isEmpty || _yearError(l10n) != null
|
||||
? null
|
||||
: () => Navigator.of(context).pop(_content),
|
||||
child: Text(
|
||||
|
|
@ -261,6 +324,89 @@ class _StudysetSheetState() extends State<_StudysetSheet> {
|
|||
],
|
||||
),
|
||||
const SizedBox(height: 24),
|
||||
Text(l10n.studysetSheetLicense, style: theme.textTheme.labelLarge),
|
||||
const SizedBox(height: 8),
|
||||
Align(
|
||||
alignment: AlignmentDirectional.centerStart,
|
||||
child: TextButton.icon(
|
||||
onPressed: _chooseLicense,
|
||||
icon: const Icon(LucideIcons.sparkles, size: 18),
|
||||
label: Text(l10n.studysetSheetLicenseHelp),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
// The licence, and its year at the end of the same line: four
|
||||
// digits need no more.
|
||||
Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Expanded(
|
||||
child: _LicenseField(
|
||||
controller: _license,
|
||||
focusNode: _licenseFocus,
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 12),
|
||||
SizedBox(
|
||||
width: 140,
|
||||
child: TextField(
|
||||
controller: _licenseYear,
|
||||
keyboardType: TextInputType.number,
|
||||
inputFormatters: [
|
||||
FilteringTextInputFormatter.digitsOnly,
|
||||
LengthLimitingTextInputFormatter(4),
|
||||
],
|
||||
decoration: InputDecoration(
|
||||
labelText: l10n.studysetSheetLicenseYear,
|
||||
suffixIcon: FieldInfoButton(
|
||||
l10n.studysetSheetLicenseYearHelper,
|
||||
),
|
||||
border: const OutlineInputBorder(),
|
||||
errorText: _yearError(l10n),
|
||||
// The error is wider than the field.
|
||||
errorMaxLines: 4,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
if (_licenseNow.license case final license?)
|
||||
if (newerCreativeCommons(license) case final current?)
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(top: 8),
|
||||
child: Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Icon(
|
||||
LucideIcons.info,
|
||||
size: 16,
|
||||
color: theme.colorScheme.onSurfaceVariant,
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
Expanded(
|
||||
child: Text(
|
||||
l10n.studysetSheetLicenseNewer(license, current),
|
||||
style: theme.textTheme.bodySmall?.copyWith(
|
||||
color: theme.colorScheme.onSurfaceVariant,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
TextField(
|
||||
controller: _licenseHolder,
|
||||
textCapitalization: TextCapitalization.words,
|
||||
decoration: InputDecoration(
|
||||
labelText: l10n.studysetSheetLicenseHolder,
|
||||
suffixIcon: FieldInfoButton(
|
||||
l10n.studysetSheetLicenseHolderHelper,
|
||||
),
|
||||
border: const OutlineInputBorder(),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 24),
|
||||
Text(
|
||||
l10n.studysetSheetLanguages,
|
||||
style: theme.textTheme.labelLarge,
|
||||
|
|
@ -309,6 +455,62 @@ class _StudysetSheetState() extends State<_StudysetSheet> {
|
|||
}
|
||||
}
|
||||
|
||||
/// The licence, typed: any text, suggesting the [knownLicenses] that hold
|
||||
/// what is typed so far.
|
||||
class const _LicenseField({
|
||||
required final TextEditingController controller,
|
||||
required final FocusNode focusNode,
|
||||
}) extends StatelessWidget {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final l10n = AppLocalizations.of(context);
|
||||
return RawAutocomplete<String>(
|
||||
textEditingController: controller,
|
||||
focusNode: focusNode,
|
||||
optionsBuilder: (value) {
|
||||
final typed = value.text.trim().toUpperCase();
|
||||
return [
|
||||
for (final license in knownLicenses)
|
||||
if (license.toUpperCase().contains(typed)) license,
|
||||
];
|
||||
},
|
||||
fieldViewBuilder: (context, controller, focusNode, onSubmitted) =>
|
||||
TextField(
|
||||
controller: controller,
|
||||
focusNode: focusNode,
|
||||
decoration: InputDecoration(
|
||||
labelText: l10n.studysetSheetLicenseLabel,
|
||||
suffixIcon: FieldInfoButton(l10n.studysetSheetLicenseHelper),
|
||||
border: const OutlineInputBorder(),
|
||||
),
|
||||
onSubmitted: (_) => onSubmitted(),
|
||||
),
|
||||
optionsViewBuilder: (context, onSelected, options) => Align(
|
||||
alignment: AlignmentDirectional.topStart,
|
||||
child: Material(
|
||||
elevation: 4,
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
child: ConstrainedBox(
|
||||
constraints: const BoxConstraints(maxHeight: 240, maxWidth: 360),
|
||||
child: ListView(
|
||||
padding: EdgeInsets.zero,
|
||||
shrinkWrap: true,
|
||||
children: [
|
||||
for (final option in options)
|
||||
ListTile(
|
||||
dense: true,
|
||||
title: Text(option),
|
||||
onTap: () => onSelected(option),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// Chooses the language of one side of the cards, by its name in the app's
|
||||
/// language, or none.
|
||||
class const _LanguageDropdown({
|
||||
|
|
|
|||
|
|
@ -0,0 +1,26 @@
|
|||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:halkyon_learn_flutter/src/feature/studyset/model/license.dart';
|
||||
|
||||
void main() {
|
||||
test('a licence is kept in one spelling: known ones as known, Creative '
|
||||
'Commons by its identifier, anything else as typed', () {
|
||||
expect(canonicalLicense('Cc By 4.0'), 'CC-BY-4.0');
|
||||
expect(canonicalLicense('cc_by_sa'), 'CC-BY-SA-4.0');
|
||||
expect(canonicalLicense('public domain'), 'CC0-1.0');
|
||||
expect(canonicalLicense('mit'), 'MIT');
|
||||
expect(canonicalLicense('arr'), 'All Rights Reserved');
|
||||
expect(canonicalLicense('cc by-nc-sa 3.0 us'), 'CC-BY-NC-SA-3.0-US');
|
||||
expect(canonicalLicense(' My own terms '), 'My own terms');
|
||||
expect(canonicalLicense(' '), isNull);
|
||||
expect(canonicalLicense(null), isNull);
|
||||
});
|
||||
|
||||
test('an older or ported Creative Commons licence has a current version; '
|
||||
'a current one, or another licence, none', () {
|
||||
expect(newerCreativeCommons('CC-BY-NC-SA-3.0-US'), 'CC-BY-NC-SA-4.0');
|
||||
expect(newerCreativeCommons('CC-BY-2.0'), 'CC-BY-4.0');
|
||||
expect(newerCreativeCommons('CC-BY-SA-4.0'), isNull);
|
||||
expect(newerCreativeCommons('CC0-1.0'), isNull);
|
||||
expect(newerCreativeCommons('MIT'), isNull);
|
||||
});
|
||||
}
|
||||
|
|
@ -644,6 +644,119 @@ void main() {
|
|||
expect((content.frontLanguage, content.backLanguage), (null, null));
|
||||
});
|
||||
|
||||
group('the licence', () {
|
||||
/// Opens the studyset `japanese`'s sheet, scrolled to its licence. The
|
||||
/// tests speak American English, as the device does.
|
||||
Future<void> editLicense(WidgetTester tester) async {
|
||||
await choose(tester, 'Edit');
|
||||
await tester.ensureVisible(find.text('Help me choose'));
|
||||
await tester.pumpAndSettle();
|
||||
}
|
||||
|
||||
StudysetContent saved() => decodeStudysetPayload(gateway.writes.last.$2);
|
||||
|
||||
testWidgets('Help me choose asks three questions and fills in the '
|
||||
'licence they come to', (tester) async {
|
||||
stored('japanese', 'Japanese');
|
||||
await showApp(tester, location: RoutePaths.studyset('japanese'));
|
||||
await tester.pumpAndSettle();
|
||||
await editLicense(tester);
|
||||
|
||||
await tester.tap(find.text('Help me choose'));
|
||||
await tester.pumpAndSettle();
|
||||
await tester.tap(find.text('Yes, share it under a license'));
|
||||
await tester.pumpAndSettle();
|
||||
await tester.tap(find.text('No'));
|
||||
await tester.pumpAndSettle();
|
||||
await tester.tap(find.text('Yes, if they share alike'));
|
||||
await tester.pumpAndSettle();
|
||||
expect(find.text('CC-BY-NC-SA-4.0'), findsOneWidget);
|
||||
expect(
|
||||
find.textContaining('the same license on adaptations'),
|
||||
findsOneWidget,
|
||||
);
|
||||
await tester.tap(find.text('Use this license'));
|
||||
await tester.pumpAndSettle();
|
||||
await tester.tap(find.text('Save'));
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
expect(saved().license, 'CC-BY-NC-SA-4.0');
|
||||
});
|
||||
|
||||
testWidgets('a typed licence is kept in its standard spelling, with its '
|
||||
'year and holder; an older Creative Commons one gets a note', (
|
||||
tester,
|
||||
) async {
|
||||
stored('japanese', 'Japanese');
|
||||
await showApp(tester, location: RoutePaths.studyset('japanese'));
|
||||
await tester.pumpAndSettle();
|
||||
await editLicense(tester);
|
||||
|
||||
await tester.enterText(
|
||||
find.widgetWithText(TextField, 'License'),
|
||||
'cc by-nc-sa 3.0 us',
|
||||
);
|
||||
await tester.enterText(find.widgetWithText(TextField, 'Year'), '999');
|
||||
await tester.pump();
|
||||
expect(
|
||||
find.text('Enter a year between 1000 and ${DateTime.now().year + 1}.'),
|
||||
findsOneWidget,
|
||||
);
|
||||
expect(
|
||||
tester
|
||||
.widget<FilledButton>(find.widgetWithText(FilledButton, 'Save'))
|
||||
.onPressed,
|
||||
isNull,
|
||||
);
|
||||
await tester.enterText(find.widgetWithText(TextField, 'Year'), '2006');
|
||||
await tester.enterText(
|
||||
find.widgetWithText(TextField, 'License holder'),
|
||||
'Tae Kim',
|
||||
);
|
||||
await tester.pump();
|
||||
expect(
|
||||
find.textContaining(
|
||||
'CC-BY-NC-SA-3.0-US is an older Creative Commons license',
|
||||
),
|
||||
findsOneWidget,
|
||||
);
|
||||
await tester.tap(find.text('Save'));
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
final content = saved();
|
||||
expect(
|
||||
(content.license, content.licenseHolder, content.licenseYear),
|
||||
('CC-BY-NC-SA-3.0-US', 'Tae Kim', 2006),
|
||||
);
|
||||
});
|
||||
|
||||
testWidgets('a new sub-studyset starts with the licence of the one it is '
|
||||
'in', (tester) async {
|
||||
stored(
|
||||
'japanese',
|
||||
'Japanese',
|
||||
license: 'CC-BY-NC-SA-3.0-US',
|
||||
licenseHolder: 'Tae Kim',
|
||||
licenseYear: 2006,
|
||||
);
|
||||
await showApp(tester, location: RoutePaths.studyset('japanese'));
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
await tester.tap(find.text('New sub-studyset'));
|
||||
await tester.pumpAndSettle();
|
||||
await tester.enterText(find.widgetWithText(TextField, 'Title *'), 'Kana');
|
||||
await tester.pump();
|
||||
await tester.tap(find.text('Create'));
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
final content = saved();
|
||||
expect(
|
||||
(content.license, content.licenseHolder, content.licenseYear),
|
||||
('CC-BY-NC-SA-3.0-US', 'Tae Kim', 2006),
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
testWidgets('with two notes or more, Reorder beside the tabs lets them be '
|
||||
'dragged into another order, until Done', (tester) async {
|
||||
stored('japanese', 'Japanese');
|
||||
|
|
|
|||
Loading…
Reference in a new issue