Merge branch 'master' into feature/exercise-crowdsourcing

# Conflicts:
#	.github/workflows/ci.yml
#	lib/models/exercises/comment.g.dart
#	lib/models/exercises/exercise.g.dart
#	lib/models/exercises/image.g.dart
#	lib/models/measurements/measurement_entry.g.dart
#	lib/models/nutrition/ingredient_weight_unit.g.dart
#	lib/models/nutrition/log.g.dart
#	lib/models/workouts/day.g.dart
#	lib/models/workouts/session.g.dart
#	lib/models/workouts/workout_plan.g.dart
#	lib/providers/exercises.dart
#	pubspec.lock
#	pubspec.yaml
#	test/utils.dart
#	test/workout/gym_mode_screen_test.dart
This commit is contained in:
Roland Geider
2022-01-15 12:58:02 +01:00
136 changed files with 1441 additions and 335 deletions

View File

@@ -1,8 +1,8 @@
name: Google Play release
on:
push:
branches:
- 'release/*'
tags:
- 'v[0-9]+.[0-9]+.[0-9]+'
jobs:
deploy_android:
@@ -14,19 +14,12 @@ jobs:
- name: Setup Java
uses: actions/setup-java@v1
with:
java-version: 12.x
java-version: 11.x
- name: Setup Ruby
uses: ruby/setup-ruby@v1
with:
ruby-version: '2.7'
- name: Decrypt config files
run: cd ./android/fastlane/envfiles && chmod +x ./decrypt_secrets.sh && ./decrypt_secrets.sh
env:
DECRYPTKEY_PLAYSTORE: ${{ secrets.DECRYPTKEY_PLAYSTORE }}
DECRYPTKEY_PLAYSTORE_SIGNING_KEY: ${{ secrets.DECRYPTKEY_PLAYSTORE_SIGNING_KEY }}
DECRYPTKEY_PROPERTIES: ${{ secrets.DECRYPTKEY_PROPERTIES }}
ruby-version: '3'
- name: Setup Flutter
uses: subosito/flutter-action@v1
@@ -34,25 +27,65 @@ jobs:
channel: 'stable'
flutter-version: '2.5.x'
- run: dart --version
- run: flutter --version
- name: Decrypt config files
run: |
cd ./fastlane/android/envfiles
chmod +x ./decrypt_secrets.sh
./decrypt_secrets.sh
env:
DECRYPTKEY_PLAYSTORE: ${{ secrets.DECRYPTKEY_PLAYSTORE }}
DECRYPTKEY_PLAYSTORE_SIGNING_KEY: ${{ secrets.DECRYPTKEY_PLAYSTORE_SIGNING_KEY }}
DECRYPTKEY_PROPERTIES: ${{ secrets.DECRYPTKEY_PROPERTIES }}
- name: Flutter info
run: |
dart --version
flutter --version
- name: Install Flutter dependencies
run: flutter pub get
- name: Extract version information
id: get_version
run: |
echo ::set-output name=VERSION_V::$(echo $GITHUB_REF | cut -d / -f 3)
echo ::set-output name=VERSION::$(echo $GITHUB_REF | cut -d / -f 3 | cut -c 2-)
echo ::set-output name=BUILD::$(flutter pub run cider version | cut -d '+' -f 2)
# Note: the original tag that triggered the workflow is in the form vX.Y.Z
# but the pubspec.yaml is committed in the commit after that one.
# Since we need the tag to point to the correct commit for other workflows
# such as f-droid we need a way to correct it. Only moving the tag
# would not work, as it would trigger this workflow again. So as
# a workaround, we use the v-tag to trigger this workflow, add a new
# one without the v and push it.
- name: Bump version
uses: maierj/fastlane-action@v2.0.0
with:
lane: setVersion
subdirectory: android
run: |
flutter pub run cider version ${{ steps.get_version.outputs.VERSION }}+${{ steps.get_version.outputs.BUILD }}
flutter pub run cider bump build
git config user.name Github-actions
git config user.email github-actions@github.com
git add .
git commit -m "Bump version to $( flutter pub run cider version )"
git tag ${{ steps.get_version.outputs.VERSION }}
git push origin HEAD:master --tags
git push origin --delete ${{ steps.get_version.outputs.VERSION_V }}
- name: Build AAB
run: flutter build appbundle --release
env:
WGER_API_KEY: ${{ secrets.WGER_API_KEY }}
- name: Run Fastlane
uses: maierj/fastlane-action@v2.0.0
- name: Upload build to Play Store
uses: maierj/fastlane-action@v2.1.0
with:
lane: production
subdirectory: android
- name: Make Github release
uses: softprops/action-gh-release@v1
with:
files: build/app/outputs/bundle/release/app-release.aab
tag_name: ${{ steps.get_version.outputs.VERSION }}
body_path: CHANGELOG.md

View File

@@ -1,6 +1,13 @@
name: Continous Integration
on: [pull_request, push]
on:
push:
branches: [ pull_request, master ]
paths:
- '**.dart'
pull_request:
branches: [ pull_request, master ]
paths:
- '**.dart'
jobs:
test:
runs-on: ubuntu-20.04

View File

@@ -4,6 +4,8 @@ on:
push:
branches:
- "master"
paths:
- '**.dart'
jobs:

10
.gitignore vendored
View File

@@ -40,8 +40,10 @@ app.*.symbols
# Obfuscation related
app.*.map.json
# Exceptions to above rules.
# Remember to never publicly share your keystore.
# See https://flutter.dev/docs/deployment/android#reference-the-keystore-from-the-app
!/packages/flutter_tools/test/data/dart_dependencies_test/**/.packages
/android/fastlane/envfiles/playstore.json
/android/fastlane/envfiles/wger.properties
/android/fastlane/envfiles/keys.jks
/fastlane/metadata/android/envfiles/playstore.json
/fastlane/metadata/android/envfiles/wger.properties
/fastlane/metadata/android/envfiles/keys.jks
/fastlane/metadata/android/envfiles/key.properties

25
CHANGELOG.md Normal file
View File

@@ -0,0 +1,25 @@
🚀 Features:
* Allow users to give meals a description #89
* Allow users to delete workout log entries #97
* Allow users to log individual ingredients/products #114
* New loading animation during first run #99
* Added reference from log to meal #105
🐛 Bug Fixes:
* Improve usability for workout logs #91
* Preselect correct time in session form #93
* Fixed infinite loader bug during auth #96
* Fix error not showing up in auth screen #102
* Fix for chart legend bug #112
* Fix for RiR slider #113
🧰 Maintenance:
* Better linting #87 #98
* Improve automatic build system for publication on play store
* Notify the user when saving entries in gym mode #92
* Consistenly display nutritional values #94
* Add minimum required server version #29
* Make order field of sets required #109

10
CHANGELOG.template.md Normal file
View File

@@ -0,0 +1,10 @@
🚀 Features:
*
🐛 Bug Fixes:
*
🧰 Maintenance:
*

View File

@@ -7,19 +7,21 @@ app written with Flutter, it talks via REST with the main server.
If you want to contribute, hop on the Discord server and say hi!
<p float="left">
<img src="https://github.com/wger-project/flutter/blob/master/android/fastlane/metadata/android/en-US/images/phoneScreenshots/01%20-%20workout%20plan.png?raw=true" width="200" />
<p>
<img src="https://raw.githubusercontent.com/wger-project/flutter/master/fastlane/metadata/android/en-US/images/phoneScreenshots/01%20-%20workout%20plan.png" width="200" />
<img src="https://github.com/wger-project/flutter/blob/master/android/fastlane/metadata/android/en-US/images/phoneScreenshots/02%20-%20workout%20log.png?raw=true" width="200" />
<img src="https://raw.githubusercontent.com/wger-project/flutter/master/fastlane/metadata/android/en-US/images/phoneScreenshots/02%20-%20workout%20log.png" width="200" />
<img src="https://github.com/wger-project/flutter/blob/master/android/fastlane/metadata/android/en-US/images/phoneScreenshots/04%20-%20nutritional%20plan.png?raw=true" width="200" />
<img src="https://raw.githubusercontent.com/wger-project/flutter/master/fastlane/metadata/android/en-US/images/phoneScreenshots/04%20-%20nutritional%20plan.png" width="200" />
</p>
## Installation
[<img src="https://play.google.com/intl/en_us/badges/images/generic/en-play-badge.png"
alt="Get it on Google Play"
height="80">](https://play.google.com/store/apps/details?id=de.wger.flutter)
[<img src="https://fdroid.gitlab.io/artwork/badge/get-it-on.png"
alt="Get it on F-Droid"
height="80">](https://f-droid.org/packages/de.wger.flutter/)
## Development
@@ -50,6 +52,9 @@ on your local instance and then run ``python3 manage.py add-user-rest theusernam
You can later list all the registered users with: ``python3 manage.py list-users-api``
[<img src="https://fdroid.gitlab.io/artwork/badge/get-it-on.png"
alt="Get it on F-Droid"
height="80">](https://f-droid.org/packages/de.wger.flutter)
### 4
Start the application with ``flutter run`` or use your IDE

View File

@@ -65,7 +65,7 @@ linter:
empty_statements: true
exhaustive_cases: true
file_names: true
flutter_style_todos: true
flutter_style_todos: false
hash_and_equals: true
implementation_imports: true
iterable_contains_unrelated_type: true

4
android/.gitignore vendored
View File

@@ -5,7 +5,3 @@ gradle-wrapper.jar
/gradlew.bat
/local.properties
GeneratedPluginRegistrant.java
# Remember to never publicly share your keystore.
# See https://flutter.dev/docs/deployment/android#reference-the-keystore-from-the-app
fastlane/envfiles/key.properties

View File

@@ -27,14 +27,14 @@ apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle"
// Keys for the android play store
def keystoreProperties = new Properties()
def keystorePropertiesFile = rootProject.file('fastlane/envfiles/key.properties')
def keystorePropertiesFile = rootProject.file('../fastlane/metadata/android/envfiles/key.properties')
if (keystorePropertiesFile.exists()) {
keystoreProperties.load(new FileInputStream(keystorePropertiesFile))
}
// Key for wger.de REST API
def wgerProperties = new Properties()
def localMapsPropertiesFile = rootProject.file('fastlane/envfiles/wger.properties')
def localMapsPropertiesFile = rootProject.file('../fastlane/metadata/android/envfiles/wger.properties')
if (localMapsPropertiesFile.exists()) {
project.logger.info('Load maps properties from local file')
localMapsPropertiesFile.withReader('UTF-8') { reader ->
@@ -55,7 +55,7 @@ if(wgerApiKey == null){
}
android {
compileSdkVersion 29
compileSdkVersion 31
sourceSets {
main.java.srcDirs += 'src/main/kotlin'
@@ -69,7 +69,7 @@ android {
// Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html).
applicationId "de.wger.flutter"
minSdkVersion 21
targetSdkVersion 30
targetSdkVersion 31
versionCode flutterVersionCode.toInteger()
versionName flutterVersionName
manifestPlaceholders = [WGER_API_KEY: wgerApiKey]

View File

@@ -26,9 +26,11 @@
android:icon="@mipmap/ic_launcher">
<meta-data android:name="wger.api_key" android:value="${WGER_API_KEY}" />
<meta-data android:name="wger.check_min_app_version" android:value="true" />
<activity
android:name=".MainActivity"
android:exported="true"
android:launchMode="singleTop"
android:theme="@style/LaunchTheme"
android:configChanges="orientation|keyboardHidden|keyboard|screenSize|smallestScreenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode"

View File

@@ -1,2 +0,0 @@
json_key_file("fastlane/envfiles/playstore.json")
package_name("de.wger.flutter")

2
fastlane/Appfile Normal file
View File

@@ -0,0 +1,2 @@
json_key_file("fastlane/metadata/android/envfiles/playstore.json")
package_name("de.wger.flutter")

View File

@@ -25,25 +25,13 @@ end
platform :android do
desc "Sets the version name and code in pubspec.yaml"
lane :setVersion do
desc "Check playstore configuration"
lane :test_configuration do
begin
old_version_code = google_play_track_version_codes(
package_name: "de.wger.flutter",
track: "production",
json_key: "./fastlane/envfiles/playstore.json",
)
puts "old_version_code: " + old_version_code.to_s
new_version_code = old_version_code.last().to_i + 1
puts "new_version_code: " + new_version_code.to_s
new_version_name = get_version_number_from_git_branch(pattern: 'release/#')
puts new_version_name.to_s
flutter_set_version(
path_to_yaml: "../pubspec.yaml",
version_name: new_version_name.to_s,
version_code: new_version_code.to_s,
upload_to_play_store(
track: 'production',
validate_only: true,
aab: './build/app/outputs/bundle/release/app-release.aab',
)
end
end
@@ -54,7 +42,7 @@ platform :android do
begin
upload_to_play_store(
track: 'production',
aab: '../build/app/outputs/bundle/release/app-release.aab',
aab: './build/app/outputs/bundle/release/app-release.aab',
skip_upload_metadata: false,
skip_upload_images: false,
skip_upload_screenshots: false,
@@ -69,7 +57,7 @@ platform :android do
begin
upload_to_play_store(
track: 'alpha',
aab: '../build/app/outputs/bundle/release/app-release.aab',
aab: './build/app/outputs/bundle/release/app-release.aab',
skip_upload_metadata: true,
skip_upload_images: true,
skip_upload_screenshots: true,

View File

@@ -3,4 +3,3 @@
# Ensure this file is checked in to source control!
gem 'fastlane-plugin-versioning'
gem 'fastlane-plugin-flutter_dart_version_manager'

View File

@@ -24,6 +24,11 @@ fastlane playstore
----
## Android
### android test_configuration
```
fastlane android test_configuration
```
Check configuration
### android production
```
fastlane android production
@@ -34,14 +39,9 @@ Upload app to production
fastlane android update_alpha
```
Upload closed alpha app and update store entry
### android deploy
```
fastlane android deploy
```
Deploy a new version to the Google Play
----
This README.md is auto-generated and will be re-generated every time [fastlane](https://fastlane.tools) is run.
This README.md is auto-generated and will be re-generated every time [_fastlane_](https://fastlane.tools) is run.
More information about fastlane can be found on [fastlane.tools](https://fastlane.tools).
The documentation of fastlane can be found on [docs.fastlane.tools](https://docs.fastlane.tools).

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

View File

@@ -3,6 +3,9 @@
# --batch to prevent interactive command
# --yes to assume "yes" for questions
# To encrypt a new version of the keys:
# gpg -c filename.json
echo "decrypting playstore API keys"
gpg --quiet --batch --yes --decrypt --passphrase="$DECRYPTKEY_PLAYSTORE" \
--output ./playstore.json playstore.json.gpg

Binary file not shown.

View File

@@ -0,0 +1,39 @@
From fitness lovers to fitness lovers get your health organized with WGER, your Workout Manager!
Have you already found your #1 fitness app and do you love to create your own sports routines? No matter what type of sporty beast you are we all have something in common: We love to keep track of our health data <3
So we dont judge you for still managing your fitness journey with your handy little workout log book but welcome to 2021!
We have developed a 100% free digital health and fitness tracker app for you, sized down to the most relevant features to make your life easier. Get started, keep training and celebrate your progress!
wger is an Open Source project and all about:
* Your Body
* Your Workouts
* Your Progress
* Your Data
Your Body:
No need to google for the ingredients of your favourite treats choose your daily meals from more than 78000 products and see the nutritional values. Add meals to the nutritional plan and keep an overview of your diet in the calendar.
Your Workouts:
You know what is best for your body. Create your own workouts out of a growing variety from 200 different exercises. Then, use the Gym Mode to guide you through the training while you log your weights with one tap.
Your Progress:
Never lose sight of your goals. Track your weight and keep your statistics.
Your Data:
wger is your personalized fitness diary but you own your data. Use the REST API to access and do amazing things with it.
Please note: This free app is not based on additional fundings and we dont ask you to donate money. More than that it is a community project which is growing constantly. So be prepared for new features anytime!
#OpenSource what does that mean?
Open Source means that the whole source code for this app and the server it talks to is free and available to anybody:
* Do you want to run wger on your own server for you or your local gym? Go ahead!
* Do you miss a feature and want to implement it? Start now!
* Do you want to check that nothing is being sent anywhere? You can!
Join our community and become a part of sport enthusiasts and IT geeks from all over the world. We keep working on adjusting and optimizing the app customized to our needs. We love your input so feel free to jump in anytime and contribute your wishes and ideas!
-> find the source code on https://github.com/wger-project
-> ask your questions or just say hello on our discord Server https://discord.gg/rPWFv6W

View File

@@ -0,0 +1,39 @@
अपना स्वास्थ का ख्याल रखिये WGER, आपका वर्कआउट मैनेजर के साथ!
क्या आपने अपना #1 फिटनेस आप चुना है और क्या आप अपना खुद स्पोर्ट रूटीन बनाते है? चाहे आपका स्वास्थ किसी भी प्रकार का हो: हम सब अपना हेल्थ डाटा को ट्रैक करना चाहते है <3
हम आपको अपने फिटनेस जर्नी को एक छोटे लॉग पे दर्ज करने के लिए जज नहीं करते। इक्कीसवीं सदी में आपका स्वागत है!
हमने आपले लिए के 100% मुफ्त डिजिटल हेल्थ और फिटनेस ट्रैकर बनाया है, सबसे महत्वपूर्ण फीचर्स के साथ जिससे आपका जीवन बोहोत आसान बन जायेगा। तो चलिए, आगे बारे, ट्रैन करे, और अपने प्रोग्रेस का मज़े लीजिये!
wger एक ओपनसोर्स प्रोजेक्ट है और निम्मलिखित चीज़ो से सम्बन्धित है:
* आपका शरीर
* आपका वर्कऑउट्स
* आपका प्रोग्रेस
* आपका डाटा
आपका शरीर:
आपको अपना मनपसंद खानो का अवयव गूगल में ढूंढ़ना नहीं पड़ेगा - अपना दैनिक भोजन 78000 से ज़्यादा प्रोडक्ट्स में से चून्हे और उनका न्यूट्रिशनल वैल्यूज देखे। नुट्रिशन प्लान में अपना भोजन दर्ज कीजिये और अपने कैलेंडर में अपना डाइट को ट्रैक करे।
आपका वर्कऑउट्स:
आपको पता है की आपके बॉडी के लिए सबसे अच्छा क्या है। अपना वर्कऑउट्स खुद बनाये। उसके बाद जिम मोड का इस्तेमाल करिये आपके ट्रेनिंग के लिए।
आपका प्रोग्रेस:
कभी अपना गोल्स के बारे में न भूले। अपना वज़न और स्टेटिस्टिक्स को ट्रैक कीजिये।
आपका डाटा:
wger आपका निजीकृत फ़िटनेस डायरी है - आप अपना डाटा के मालिक है। हमारा REST API का प्रयोग करके कुछ मज़ेदार काम करे।
क्रियण नोट कीजिये: यह मुफ्त आप कोई भी एडिशनल फ़ंडिंग्स पे नहीं चलता और हम आपको पैसा डोनेट करने के लिए नहीं पूछते। यह एक कम्युनिटी प्रोजेक्ट है को हर वक्त बढ़ रहा है। तो नए फीटर्स के लिए हमेशा तैयार रहे!
#ओपनसोर्स - इसका अर्थ क्या है?
ओपनसोर्स का अर्थ यह है की इस अप्प का पूरा सोर्स कोड और जिस सर्वर से यह बात करता है - सबके लिए मुफ्त और उपलब्ध है।
* क्या आप wger को अपने लोकल सर्वर में या फिर अपने स्थानीय जिम के लिए प्रयोग करना चाहते है? आगे बढ़िए!
* क्या आपको किसी फीचर का कमी नज़र आया है और आप उसको खुद इम्प्लीमेंट करना चाहते है? अभी शुरू कीजिये!
* क्या आपको देखना है की कुछ भी कही भी नहीं भेजा जा रहा है? आप कर सकते है!
हमारा कम्युनिटी ज्वाइन करिये और संसार के अन्य स्पोर्ट्स एवं IT गीक्स के साथ जुड़े। हम इस अप्प को अपने खुद के लिए ऑप्टिमिसे और बदल ते रहते है। आपका इनपुट भी चाहिए तो कृपया हमारे कम्युनिटी ज्वाइन करिये और अपना विचार धरा हमें बोले।
-> सोर्स कोड यहाँ पाए : https://github.com/wger-project
-> प्रश्न पूछे या फिर केवल नमस्ते बोले हमारे डिस्कॉर्ड सर्वर पे : https://discord.gg/rPWFv6W

View File

@@ -0,0 +1 @@
फिटनेस/वर्कआउट, नुट्रिशन और वज़न ट्रैकर

View File

@@ -0,0 +1 @@
wger Workout Manager

View File

@@ -0,0 +1,39 @@
От любителей фитнеса для любителей фитнеса - организуйте свое здоровье с WGER, вашим менеджером тренировок!
Вы уже нашли свое фитнес-приложение №1 и любите создавать свои собственные спортивные программы? Независимо от того, какой вы спортивный зверь - у всех нас есть нечто общее: мы любим отслеживать данные о своем здоровье <3
Поэтому мы не осуждаем вас за то, что вы все еще ведете свой фитнес-путь с помощью удобного маленького журнала тренировок, но добро пожаловать в 2021 год!
Мы разработали для вас на 100% свободное цифровое приложение для отслеживания состояния здоровья и фитнес-тренировок, сократив его до самых необходимых функций, чтобы облегчить вам жизнь. Начните, продолжайте тренироваться и отмечайте свой прогресс!
wger - это проект с открытым исходным кодом и все о:
* Вашем теле
* Ваших тренировках
* Вашем прогрессе
* Ваших данных
Вашем теле:
Не нужно "гуглить" ингредиенты любимых лакомств - выбирайте ежедневные блюда из более чем 78000 продуктов и смотрите их пищевую ценность. Добавляйте блюда в план питания и следите за своим рационом в календаре.
Ваших тренировках:
Вы знаете, что лучше всего подходит для вашего тела. Создавайте свои собственные тренировки из растущего разнообразия 200 различных упражнений. Затем используйте режим тренажерного зала, чтобы направлять вас во время тренировки, а также регистрируйте свои веса одним касанием.
Вашем прогрессе:
Никогда не теряйте из виду свои цели. Отслеживайте свой вес и ведите статистику.
Ваших данных:
wger - это ваш персональный фитнес-дневник - но ваши данные принадлежат вам. Используйте REST API для доступа к ним и делайте с ними удивительные вещи.
Обратите внимание: это свободное приложение не основано на дополнительном финансировании, и мы не просим вас жертвовать деньги. Более того, это проект сообщества, который постоянно растет. Так что будьте готовы к появлению новых функций в любое время!
#OpenSource #ОткрытыйИсходныйКод - что это значит?
Открытый исходный код означает, что весь исходный код этого приложения и сервера, с которым оно работает, свободен и доступен любому:
* Вы хотите запустить wger на собственном сервере для себя или своего спортзала? Вперед!
* Вам не хватает какой-то функции и вы хотите ее реализовать? Начните прямо сейчас!
* Хотите проверить, что ничего никуда не отправляется? Вы можете!
Присоединяйтесь к нашему сообществу и станьте частью спортивных энтузиастов и IT-гиков со всего мира. Мы продолжаем работать над корректировкой и оптимизацией приложения в соответствии с нашими потребностями. Нам нравится ваш вклад, поэтому не стесняйтесь вступать в сообщество в любое время и высказывать свои пожелания и идеи!
-> найти исходный код на https://github.com/wger-project
-> задавайте свои вопросы или просто поздоровайтесь на нашем discord-сервере https://discord.gg/rPWFv6W

View File

@@ -0,0 +1 @@
Трекер фитнеса/тренировок, питания и веса

View File

@@ -0,0 +1,39 @@
Fitness tutkunlarından fitness severlere - Egzersiz Yöneticiniz WGER ile sağlığınızı organize edin!
1 numaralı fitness uygulamanızı zaten buldunuz mu ve kendi spor rutinlerinizi oluşturmayı seviyor musunuz? Ne tür bir sportif canavar olursanız olun - hepimizin ortak bir yanı var: Sağlık verilerimizi takip etmeyi seviyoruz <3
Bu nedenle, fitness yolculuğunuzu kullanışlı küçük egzersiz kayıt defterinizle hala yönettiğiniz için sizi yargılamıyoruz, ancak 2021'e hoş geldiniz!
Sizin için hayatınızı kolaylaştırmak için en alakalı özelliklere göre boyutlandırılmış %100 ücretsiz bir dijital sağlık ve fitness takip uygulaması geliştirdik. Başlayın, antrenmana devam edin ve ilerlemenizi kutlayın!
wger bir Açık Kaynak projesidir ve her şey:
* Vucüdun
* Antrenmanlarınız
* Senin ilerlemen
* Verileriniz
Vucüdun:
En sevdiğiniz ikramların içeriğini Google'da aramanıza gerek yok - 78000'den fazla ürün arasından günlük öğünlerinizi seçin ve besin değerlerini görün. Beslenme planına yemek ekleyin ve takvimde diyetinizin bir özetini tutun.
Antrenmanlarınız:
Vücudunuz için en iyisini siz bilirsiniz. 200 farklı egzersizden artan çeşitlilikten kendi egzersiz programlarınızı oluşturun. Ardından, ağırlıklarınızı tek dokunuşla kaydederken egzersiz boyunca size rehberlik etmesi için Spor Salonu Modunu kullanın.
Senin ilerlemen:
Hedeflerinizi asla gözden kaçırmayın. Kilonuzu takip edin ve istatistiklerinizi saklayın.
Verileriniz:
wger, kişiselleştirilmiş fitness günlüğünüzdür - ancak verilerinizin sahibi sizsiniz. Erişmek ve onunla harika şeyler yapmak için REST API'yi kullanın.
Lütfen dikkat: Bu ücretsiz uygulama ek fonlara dayalı değildir ve sizden para bağışlamanızı istemiyoruz. Dahası, sürekli büyüyen bir topluluk projesidir. Bu yüzden her zaman yeni özelliklere hazır olun!
#OpenSource bu ne anlama geliyor?
ık Kaynak, bu uygulamanın ve konuştuğu sunucunun tüm kaynak kodunun ücretsiz ve herkes tarafından kullanılabilir olduğu anlamına gelir:
* Kendi sunucunuzda veya yerel spor salonunuz için wger çalıştırmak ister misiniz? Devam etmek!
* Bir özelliği özlüyor ve uygulamak istiyor musunuz? Şimdi başla!
* Hiçbir şeyin hiçbir yere gönderilmediğini kontrol etmek ister misiniz? Yapabilirsiniz!
Topluluğumuza katılın ve dünyanın her yerinden spor meraklılarının ve BT meraklılarının bir parçası olun. İhtiyaçlarımıza göre özelleştirilmiş uygulamayı ayarlamak ve optimize etmek için çalışmaya devam ediyoruz. Katkılarınızı seviyoruz, bu yüzden istediğiniz zaman atlamaktan ve dilekleriniz ve fikirlerinizle katkıda bulunmaktan çekinmeyin!
-> https://github.com/wger-project adresinde kaynak kodunu bulun
-> sorularınızı sorun veya sadece discord sunucumuzda merhaba deyin https://discord.gg/rPWFv6W

View File

@@ -0,0 +1 @@
Fitness/egzersiz, beslenme ve kilo takibi

View File

@@ -0,0 +1 @@
wger Workout Manager

View File

@@ -0,0 +1,39 @@
每一个健身爱好者 - 与您的健身教练 WGER 一起,让你更健康!
你是否已经找到了排名第一的健身应用程序,并且喜欢创建自己的运动习惯? 无论你是哪种运动野兽——我们都有一个共同点:我们喜欢跟踪我们的健康数据
因此,我们不会因为你仍然使用小笔记本来管理你的健身旅程而评判你,但欢迎来到 2021 年!
我们为你开发了 100% 免费的数字健康和健身追踪器应用程序,压缩到最相关的功能,让你的生活更轻松。开始吧,继续训练并庆祝你的进步!
wger 是一个开源项目,关于:
* 你的身体
* 你的锻炼
* 你的进步
* 你的数据
你的身体:
无需在谷歌上搜索你最喜欢的食物的成分——从 78000 多种产品中选择你的日常膳食并查看营养价值。将膳食添加到营养计划中,并在日历中概述你的饮食。
你的锻炼:
你知道什么对你的身体最好。从 200 种不同的练习中选择越来越多的练习来创建你自己的锻炼。然后,使用健身房模式指导你完成训练,同时一键记录你的体重。
你的进步:
永远不要忘记你的目标。跟踪你的体重并保留你的统计数据。
你的数据:
wger 是你的个性化健身日记——但你拥有自己的数据。使用 REST API 来访问它并用它做惊人的事情。
请注意:这个免费的应用程序不是基于额外的资金,我们不要求你捐款。不仅如此,它还是一个不断发展的社区项目。因此,请随时为新功能做好准备!
#OpenSource 这是什么意思?
开源意味着这个应用程序的整个源代码和它与之通信的服务器都是免费的,任何人都可以使用:
* 你想在自己的服务器上为你还是为你当地的健身房运行 wger都可以
* 你是否错过了某个功能并想要实现它?现在开始!
* 你想检查任何地方都没有发送任何东西吗?你可以!
加入我们的社区,成为来自世界各地的体育爱好者和 IT 极客的一份子。我们一直致力于调整和优化根据我们的需求定制的应用程序。我们喜欢你的意见,因此请随时加入并贡献你的愿望和想法!
-> 在 https://github.com/wger-project 上找到源代码
-> 在我们的 Discord 服务器上提问或打个招呼 https://discord.gg/rPWFv6W

View File

@@ -0,0 +1 @@
健身/锻炼、营养和体重追踪器

View File

@@ -26,6 +26,10 @@ const double ICON_SIZE_SMALL = 20;
/// Default wger server during login
const DEFAULT_SERVER = 'https://wger.de';
/// Keys used in the android manifest
const MANIFEST_KEY_API = 'wger.api_key';
const MANIFEST_KEY_CHECK_UPDATE = 'wger.check_min_app_version';
/// Default weight unit is "kg"
const DEFAULT_WEIGHT_UNIT = 1;
@@ -72,6 +76,3 @@ const ENERGY_CARBOHYDRATES = 4;
/// kcal per gram of fat (approx)
const ENERGY_FAT = 9;
/// Flag to check for updates to the new version.
const ENABLED_UPDATE = false;

View File

@@ -19,7 +19,6 @@
/// Calculates the number of plates needed to reach a specific weight
List<num> plateCalculator(num totalWeight, num barWeight, List<num> plates) {
final List<num> ans = [];
final platesCount = plates.length;
// Weight is less than the bar
if (totalWeight < barWeight) {

View File

@@ -96,6 +96,6 @@ void launchURL(String url, BuildContext context) async {
await canLaunch(url)
? await launch(url)
: ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text("Could not open $url.")),
SnackBar(content: Text('Could not open $url.')),
);
}

View File

@@ -478,7 +478,7 @@
},
"percentEnergy": "Prozent der Energie",
"@percentEnergy": {},
"difference": "Unterschied",
"difference": "Differenz",
"@difference": {},
"logged": "Protokolliert",
"@logged": {
@@ -531,5 +531,11 @@
"recentlyUsedIngredients": "Kürzlich hinzugefügte Zutaten",
"@recentlyUsedIngredients": {
"description": "A message when a user adds a new ingredient to a meal."
}
},
"searchIngredient": "Zutat suchen",
"@searchIngredient": {
"description": "Label on ingredient search form"
},
"logIngredient": "Im Nährwerttagebuch speichern",
"@logIngredient": {}
}

View File

@@ -526,5 +526,11 @@
"recentlyUsedIngredients": "Ingrédients récemment ajoutés",
"@recentlyUsedIngredients": {
"description": "A message when a user adds a new ingredient to a meal."
}
},
"searchIngredient": "Rechercher un ingrédient",
"@searchIngredient": {
"description": "Label on ingredient search form"
},
"logIngredient": "Sauvegarder dans le journal nutritionnel",
"@logIngredient": {}
}

26
lib/l10n/app_hi.arb Normal file
View File

@@ -0,0 +1,26 @@
{
"login": "लोग इन",
"@login": {
"description": "Text for login button"
},
"logout": "लोग आउट",
"@logout": {
"description": "Text for logout button"
},
"useDefaultServer": "डिफ़ॉल्ट सर्वर का प्रयोग करें",
"@useDefaultServer": {
"description": "Toggle button allowing users to switch between the default and a custom wger server"
},
"register": "रजिस्टर",
"@register": {
"description": "Text for registration button"
},
"useCustomServer": "कस्टम सर्वर का प्रयोग करें",
"@useCustomServer": {
"description": "Toggle button allowing users to switch between the default and a custom wger server"
},
"invalidUrl": "कृपया एक वैलिड URL दीजिये",
"@invalidUrl": {
"description": "Error message when the user enters an invalid URL, e.g. in the login form"
}
}

View File

@@ -514,5 +514,11 @@
"recentlyUsedIngredients": "Nedavno dodani sastojci",
"@recentlyUsedIngredients": {
"description": "A message when a user adds a new ingredient to a meal."
},
"logIngredient": "Spremi u dnevnik prehrane",
"@logIngredient": {},
"searchIngredient": "Traži sastojak",
"@searchIngredient": {
"description": "Label on ingredient search form"
}
}

View File

@@ -268,5 +268,7 @@
"logout": "Logg ut",
"@logout": {
"description": "Text for logout button"
}
},
"difference": "Forskjell",
"@difference": {}
}

524
lib/l10n/app_ru.arb Normal file
View File

@@ -0,0 +1,524 @@
{
"useCustomServer": "Использовать пользовательский сервер",
"@useCustomServer": {
"description": "Toggle button allowing users to switch between the default and a custom wger server"
},
"email": "Адрес электронной почты",
"@email": {},
"labelWorkoutPlans": "Планы тренировок",
"@labelWorkoutPlans": {
"description": "Title for screen workout plans"
},
"registerInstead": "Зарегистроваться вместо этого",
"@registerInstead": {},
"loginInstead": "Войти вместо этого",
"@loginInstead": {},
"labelDashboard": "Панель управления",
"@labelDashboard": {
"description": "Title for screen dashboard"
},
"muscles": "Мышцы",
"@muscles": {
"description": "(main) muscles trained by an exercise"
},
"reps": "Повтор",
"@reps": {
"description": "Shorthand for repetitions, used when space constraints are tighter"
},
"rir": "ПвЗ",
"@rir": {
"description": "Shorthand for Repetitions In Reserve"
},
"rirNotUsed": "ПвЗ не используется",
"@rirNotUsed": {
"description": "Label used in RiR slider when the RiR value is not used/saved for the current setting or log"
},
"notes": "Заметки",
"@notes": {
"description": "Personal notes, e.g. for a workout session"
},
"workoutSession": "Тренировка",
"@workoutSession": {
"description": "A (logged) workout session"
},
"newDay": "Новый день",
"@newDay": {},
"selectExercises": "Если вы хотите сделать суперсет, вы можете найти несколько упражнений, они будут сгруппированы вместе",
"@selectExercises": {},
"plateCalculator": "Пластины",
"@plateCalculator": {
"description": "Label used for the plate calculator in the gym mode"
},
"save": "Сохранить",
"@save": {},
"logMeal": "Записать это блюдо",
"@logMeal": {},
"nutritionalPlan": "План питания",
"@nutritionalPlan": {},
"noNutritionalPlans": "У вас нет планов питания",
"@noNutritionalPlans": {
"description": "Message shown when the user has no nutritional plans"
},
"measurementEntriesHelpText": "Единица, используемая для измерения в категории, например 'см' или '%'",
"@measurementEntriesHelpText": {},
"value": "Значение",
"@value": {
"description": "The value of a measurement entry"
},
"time": "Время",
"@time": {
"description": "The time of a meal or workout"
},
"timeStart": "Время начала",
"@timeStart": {
"description": "The starting time of a workout"
},
"ingredient": "Ингредиент",
"@ingredient": {},
"energy": "Энергия",
"@energy": {
"description": "Energy in a meal, ingredient etc. e.g. in kJ"
},
"energyShort": "Э",
"@energyShort": {
"description": "The first letter or short name of the word 'Energy', used in overviews"
},
"macronutrients": "Макроэлементы",
"@macronutrients": {},
"logged": "Записано",
"@logged": {
"description": "Header for the column of 'logged' nutritional values, i.e. what was eaten"
},
"difference": "Разница",
"@difference": {},
"kJ": "кДж",
"@kJ": {
"description": "Energy in a meal in kilo joules, kJ"
},
"g": "г",
"@g": {
"description": "Abbreviation for gram"
},
"goToDetailPage": "Перейти на страницу сведений",
"@goToDetailPage": {},
"aboutSourceTitle": "Исходный код",
"@aboutSourceTitle": {
"description": "Title for source code section in the about dialog"
},
"aboutContactUsTitle": "Скажите привет!",
"@aboutContactUsTitle": {
"description": "Title for contact us section in the about dialog"
},
"aboutTranslationTitle": "Перевод",
"@aboutTranslationTitle": {
"description": "Title for translation section in the about dialog"
},
"calendar": "Календарь",
"@calendar": {},
"enterValue": "Пожалуйста, введите значение",
"@enterValue": {
"description": "Error message when the user hasn't entered a value on a required field"
},
"selectExercise": "Пожалуйста, выберите упражнение",
"@selectExercise": {
"description": "Error message when the user hasn't selected an exercise in the form"
},
"enterCharacters": "Пожалуйста, введите между {min} и {max} символами",
"@enterCharacters": {
"description": "Error message when the user hasn't entered the correct number of characters in a form",
"type": "text",
"placeholders": {
"min": {},
"max": {}
}
},
"nrOfSets": "Количество сетов на упражнение: {nrOfSets}",
"@nrOfSets": {
"description": "Label shown on the slider where the user selects the nr of sets",
"type": "text",
"placeholders": {
"nrOfSets": {}
}
},
"selectIngredient": "Пожалуйста, выберите ингредиент",
"@selectIngredient": {
"description": "Error message when the user hasn't selected an ingredient from the autocompleter"
},
"recentlyUsedIngredients": "Недавно добавленные ингредиенты",
"@recentlyUsedIngredients": {
"description": "A message when a user adds a new ingredient to a meal."
},
"selectImage": "Пожалуйста, выберите изображение",
"@selectImage": {
"description": "Label and error message when the user hasn't selected an image to save"
},
"takePicture": "Сфотографировать",
"@takePicture": {},
"chooseFromLibrary": "Выбрать из библиотеки фотографий",
"@chooseFromLibrary": {},
"gallery": "Галерея",
"@gallery": {},
"addImage": "Добавить изображение",
"@addImage": {},
"appUpdateTitle": "Требуется обновление",
"@appUpdateTitle": {},
"passwordTooShort": "Пароль слишком короткий",
"@passwordTooShort": {
"description": "Error message when the user a password that is too short"
},
"category": "Категория",
"@category": {
"description": "Category for an exercise, ingredient, etc."
},
"noWorkoutPlans": "У вас нет планов тренировок",
"@noWorkoutPlans": {
"description": "Message shown when the user has no workout plans"
},
"login": "Войти",
"@login": {
"description": "Text for login button"
},
"register": "Зарегистрироваться",
"@register": {
"description": "Text for registration button"
},
"password": "Пароль",
"@password": {},
"logout": "Выйти",
"@logout": {
"description": "Text for logout button"
},
"useDefaultServer": "Использовать сервер по умолчанию",
"@useDefaultServer": {
"description": "Toggle button allowing users to switch between the default and a custom wger server"
},
"invalidUrl": "Пожалуйста, введите действительный URL",
"@invalidUrl": {
"description": "Error message when the user enters an invalid URL, e.g. in the login form"
},
"usernameValidChars": "Имя пользователя может содержать только буквы, цифры и символы @, +, ., - и _",
"@usernameValidChars": {
"description": "Error message when the user tries to register a username with forbidden characters"
},
"passwordsDontMatch": "Пароли не совпадают",
"@passwordsDontMatch": {
"description": "Error message when the user enters two different passwords during registration"
},
"confirmPassword": "Подтверждение пароля",
"@confirmPassword": {},
"invalidEmail": "Пожалуйста, введите действительный адрес электронной почты",
"@invalidEmail": {
"description": "Error message when the user enters an invalid email"
},
"username": "Имя пользователя",
"@username": {},
"invalidUsername": "Пожалуйста, введите действительное имя пользователя",
"@invalidUsername": {
"description": "Error message when the user enters an invalid username"
},
"customServerHint": "Введите адрес вашего собственного сервера, иначе будет использован адрес по умолчанию",
"@customServerHint": {
"description": "Hint text for the form where the users can enter their own wger instance"
},
"reset": "Сброс",
"@reset": {
"description": "Button text allowing the user to reset the entered values to the default"
},
"labelBottomNavWorkout": "Тренировка",
"@labelBottomNavWorkout": {
"description": "Label used in bottom navigation, use a short word"
},
"customServerUrl": "URL-адрес экземпляра wger",
"@customServerUrl": {
"description": "Label in the form where the users can enter their own wger instance"
},
"labelBottomNavNutrition": "Питание",
"@labelBottomNavNutrition": {
"description": "Label used in bottom navigation, use a short word"
},
"labelWorkoutPlan": "План тренировки",
"@labelWorkoutPlan": {
"description": "Title for screen workout plan"
},
"labelWorkoutLogs": "Журналы тренировок",
"@labelWorkoutLogs": {
"description": "(Workout) logs"
},
"exercise": "Упражнение",
"@exercise": {
"description": "An exercise for a workout"
},
"successfullySaved": "Сохранено",
"@successfullySaved": {
"description": "Message when an item was successfully saved"
},
"successfullyDeleted": "Удалено",
"@successfullyDeleted": {
"description": "Message when an item was successfully deleted"
},
"searchExercise": "Найдите упражнение, которое нужно добавить",
"@searchExercise": {
"description": "Label on set form. Selected exercises are added to the set"
},
"equipment": "Оборудование",
"@equipment": {
"description": "Equipment needed to perform an exercise"
},
"musclesSecondary": "Вторичные мышцы",
"@musclesSecondary": {
"description": "secondary muscles trained by an exercise"
},
"newWorkout": "Новый план тренировок",
"@newWorkout": {
"description": "Header when adding a new workout"
},
"dayDescriptionHelp": "Описание того, что делается в этот день (например, 'день подтягиваний') или какие части тела тренируются (например, 'грудь и плечи')",
"@dayDescriptionHelp": {},
"weightUnit": "Единица веса",
"@weightUnit": {},
"repetitionUnit": "Единица повторения",
"@repetitionUnit": {},
"repetitions": "Повторений",
"@repetitions": {
"description": "Repetitions for an exercise set"
},
"comment": "Комментарий",
"@comment": {
"description": "Comment, additional information"
},
"impression": "Впечатление",
"@impression": {
"description": "General impression (e.g. for a workout session) such as good, bad, etc."
},
"set": "Сет",
"@set": {
"description": "A set in a workout plan"
},
"gymMode": "Режим тренажерного зала",
"@gymMode": {
"description": "Label when starting the gym mode"
},
"jumpTo": "Перейти к",
"@jumpTo": {
"description": "Imperative. Label used in popup allowing the user to jump to a specific exercise while in the gym mode"
},
"logHelpEntriesUnits": "Обратите внимание, что в таблицу заносятся только записи с единицей веса (кг или фунты) и повторениями, другие комбинации, такие как время или до отказа, здесь не учитываются.",
"@logHelpEntriesUnits": {},
"pause": "Пауза",
"@pause": {
"description": "Noun, not an imperative! Label used for the pause when using the gym mode"
},
"plateCalculatorNotDivisible": "Невозможно достичь веса с имеющимися пластинами",
"@plateCalculatorNotDivisible": {
"description": "Error message when the current weight is not reachable with plates (e.g. 33.1 kg)"
},
"todaysWorkout": "Ваша тренировка сегодня",
"@todaysWorkout": {},
"logHelpEntries": "Если в один день есть несколько записей с одинаковым количеством повторений, но разными весами, то на диаграмме отображается только запись с большим весом.",
"@logHelpEntries": {},
"description": "Описание",
"@description": {},
"weight": "Вес",
"@weight": {
"description": "The weight of a workout log or body weight entry"
},
"name": "Имя",
"@name": {
"description": "Name for a workout or nutritional plan"
},
"addIngredient": "Добавить ингредиент",
"@addIngredient": {},
"nutritionalPlans": "Планы питания",
"@nutritionalPlans": {},
"timeStartAhead": "Время начала не может быть раньше времени окончания",
"@timeStartAhead": {},
"logIngredient": "Сохранить в дневнике питания",
"@logIngredient": {},
"searchIngredient": "Поиск ингредиента",
"@searchIngredient": {
"description": "Label on ingredient search form"
},
"nutritionalDiary": "Дневник питания",
"@nutritionalDiary": {},
"measurements": "Измерения",
"@measurements": {
"description": "Categories for the measurements such as biceps size, body fat, etc."
},
"anErrorOccurred": "Произошла ошибка!",
"@anErrorOccurred": {},
"measurement": "Измерение",
"@measurement": {},
"measurementCategoriesHelpText": "Категория измерения, например 'бицепс' или 'жир'",
"@measurementCategoriesHelpText": {},
"date": "Дата",
"@date": {
"description": "The date of a workout log or body weight entry"
},
"start": "Начать",
"@start": {
"description": "Label on button to start the gym mode (i.e., an imperative)"
},
"timeEnd": "Время окончания",
"@timeEnd": {
"description": "The end time of a workout"
},
"kcal": "ккал",
"@kcal": {
"description": "Energy in a meal in kilocalories, kcal"
},
"edit": "Редактировать",
"@edit": {},
"delete": "Удалить",
"@delete": {},
"confirmDelete": "Вы уверены, что хотите удалить '{toDelete}'?",
"@confirmDelete": {
"description": "Confirmation text before the user deletes an object",
"type": "text",
"placeholders": {
"toDelete": {}
}
},
"setUnitsAndRir": "Установите единицы измерения и ПвЗ",
"@setUnitsAndRir": {
"description": "Label shown on the slider where the user can toggle showing units and RiR",
"type": "text"
},
"planned": "Запланировано",
"@planned": {
"description": "Header for the column of 'planned' nutritional values, i.e. what should be eaten"
},
"fatShort": "Ж",
"@fatShort": {
"description": "The first letter or short name of the word 'Fat', used in overviews"
},
"gPerBodyKg": "г на кг тела",
"@gPerBodyKg": {
"description": "Label used for total sums of e.g. calories or similar in grams per Kg of body weight"
},
"total": "Всего",
"@total": {
"description": "Label used for total sums of e.g. calories or similar"
},
"fibres": "Клетчатка",
"@fibres": {},
"percentEnergy": "Процент энергии",
"@percentEnergy": {},
"proteinShort": "Б",
"@proteinShort": {
"description": "The first letter or short name of the word 'Protein', used in overviews"
},
"carbohydratesShort": "У",
"@carbohydratesShort": {
"description": "The first letter or short name of the word 'Carbohydrates', used in overviews"
},
"sugars": "Сахар",
"@sugars": {},
"saturatedFat": "Насыщенные жиры",
"@saturatedFat": {},
"sodium": "Натрий",
"@sodium": {},
"amount": "Количество",
"@amount": {
"description": "The amount (e.g. in grams) of an ingredient in a meal"
},
"unit": "Единица",
"@unit": {
"description": "The unit used for a repetition (kg, time, etc.)"
},
"noWeightEntries": "У вас нет записей о весе",
"@noWeightEntries": {
"description": "Message shown when the user has no logged weight entries"
},
"supersetWith": "суперсет с",
"@supersetWith": {
"description": "Text used between exercise cards when adding a new set. Translate as something like 'in a superset with'"
},
"newEntry": "Новая запись",
"@newEntry": {
"description": "Title when adding a new entry such as a weight or log entry"
},
"loadingText": "Загрузка…",
"@loadingText": {
"description": "Text to show when entries are being loaded in the background: Loading..."
},
"newNutritionalPlan": "Новый план питания",
"@newNutritionalPlan": {},
"toggleDetails": "Переключить сведения",
"@toggleDetails": {
"description": "Switch to toggle detail / overview"
},
"aboutDescription": "Спасибо за использование wger! wger - это совместный проект с открытым исходным кодом, созданный фитнес энтузиастами со всего мира.",
"@aboutDescription": {
"description": "Text in the about dialog"
},
"aboutContactUsText": "Если вы хотите пообщаться с нами, зайдите на сервер Discord и свяжитесь с нами",
"@aboutContactUsText": {
"description": "Text for contact us section in the about dialog"
},
"aboutTranslationText": "Это приложение переведено на weblate. Если вы тоже хотите помочь, нажмите на ссылку и начните переводить",
"@aboutTranslationText": {
"description": "Text for translation section in the about dialog"
},
"aboutSourceText": "Получите исходный код этого приложения и его сервера на github",
"@aboutSourceText": {
"description": "Text for source code section in the about dialog"
},
"aboutBugsTitle": "Есть проблема или идея?",
"@aboutBugsTitle": {
"description": "Title for bugs section in the about dialog"
},
"enterValidNumber": "Пожалуйста, введите правильное число",
"@enterValidNumber": {
"description": "Error message when the user has submitted an invalid number (e.g. '3,.,.,.')"
},
"aboutBugsText": "Свяжитесь с нами, если что-то пошло не так или если вам кажется, что какой-то функции не хватает.",
"@aboutBugsText": {
"description": "Text for bugs section in the about dialog"
},
"goToToday": "Перейти на сегодня",
"@goToToday": {
"description": "Label on button to jump back to 'today' in the calendar widget"
},
"enterRepetitionsOrWeight": "Пожалуйста, укажите количество повторений или вес хотя бы для одного из сетов",
"@enterRepetitionsOrWeight": {
"description": "Error message when the user hasn't filled in the forms for exercise sets"
},
"optionsLabel": "Опции",
"@optionsLabel": {
"description": "Label for the popup with general app options"
},
"sameRepetitions": "Если вы делаете одинаковые повторения и вес для всех сетов, вы можете просто заполнить одну строку. Например, для 4 сетов просто введите 10 повторений, это автоматически превратится в \"4 x 10\".",
"@sameRepetitions": {},
"newSet": "Новый сет",
"@newSet": {
"description": "Header when adding a new set to a workout day"
},
"dataCopied": "Данные копируются в новую запись",
"@dataCopied": {
"description": "Snackbar message to show on copying data to a new log entry"
},
"appUpdateContent": "Эта версия приложения не совместима с сервером, пожалуйста, обновите ваше приложение.",
"@appUpdateContent": {},
"setNr": "Сет {nr}",
"@setNr": {
"description": "Header in form indicating the number of the current set. Can also be translated as something like 'Set Nr. xy'.",
"type": "text",
"placeholders": {
"nr": {}
}
},
"addSet": "Добавить сет",
"@addSet": {
"description": "Label for the button that adds a set (to a workout day)"
},
"addMeal": "Добавить блюдо",
"@addMeal": {},
"mealLogged": "Блюдо записано в дневник",
"@mealLogged": {},
"protein": "Белки",
"@protein": {},
"carbohydrates": "Углеводы",
"@carbohydrates": {},
"fat": "Жиры",
"@fat": {}
}

View File

@@ -164,5 +164,203 @@
"placeholders": {
"nr": {}
}
}
},
"sameRepetitions": "Eğer tüm setler için aynıırlıklarla aynı setleri yapıyorsanız, sadece bir satır doldurabilirsiniz. Örneğin, 4 set için sadece 10 girin, bu otomatik olarak \"4 x 10\"a dönüşüyor.",
"@sameRepetitions": {},
"selectExercises": "Eğer bir süper set yapmak istiyorsanız, birkaç egzersiz için arama yapabilirsiniz, birlikte gruplanacaklardır",
"@selectExercises": {},
"logHelpEntries": "Tek bir günde, aynı sayıda tekrara sahip ancak farklıırlıklara sahip birden fazla giriş varsa, diyagramda yalnızca daha yüksek ağırlığa sahip giriş gösterilir.",
"@logHelpEntries": {},
"addSet": "Set ekle",
"@addSet": {
"description": "Label for the button that adds a set (to a workout day)"
},
"date": "Tarih",
"@date": {
"description": "The date of a workout log or body weight entry"
},
"time": "Zaman",
"@time": {
"description": "The time of a meal or workout"
},
"logHelpEntriesUnits": "Yalnızca ağırlık birimi (kg veya lb) ve tekrarları olan girişlerin çizelgelendirildiğini, zaman veya arızaya kadar olan diğer kombinasyonların burada göz ardı edildiğini unutmayın.",
"@logHelpEntriesUnits": {},
"nutritionalPlans": "Beslenme planları",
"@nutritionalPlans": {},
"energyShort": "E",
"@energyShort": {
"description": "The first letter or short name of the word 'Energy', used in overviews"
},
"total": "Toplam",
"@total": {
"description": "Label used for total sums of e.g. calories or similar"
},
"kJ": "kilo joule",
"@kJ": {
"description": "Energy in a meal in kilo joules, kJ"
},
"anErrorOccurred": "Bir hata oluştu!",
"@anErrorOccurred": {},
"plateCalculator": "Tabaklar",
"@plateCalculator": {
"description": "Label used for the plate calculator in the gym mode"
},
"logIngredient": "Beslenme günlüğüne kaydet",
"@logIngredient": {},
"comment": "Yorum",
"@comment": {
"description": "Comment, additional information"
},
"notes": "Notlar",
"@notes": {
"description": "Personal notes, e.g. for a workout session"
},
"workoutSession": "Egzersiz Seansı",
"@workoutSession": {
"description": "A (logged) workout session"
},
"newDay": "Yeni gün",
"@newDay": {},
"newSet": "Yeni set",
"@newSet": {
"description": "Header when adding a new set to a workout day"
},
"gymMode": "Spor salonu modu",
"@gymMode": {
"description": "Label when starting the gym mode"
},
"plateCalculatorNotDivisible": "Varolan tabaklar ile istenilen ağırlığa ulaşmak mümkün değil",
"@plateCalculatorNotDivisible": {
"description": "Error message when the current weight is not reachable with plates (e.g. 33.1 kg)"
},
"pause": "Duraklat",
"@pause": {
"description": "Noun, not an imperative! Label used for the pause when using the gym mode"
},
"jumpTo": "Buraya git",
"@jumpTo": {
"description": "Imperative. Label used in popup allowing the user to jump to a specific exercise while in the gym mode"
},
"todaysWorkout": "Günlük antrenmanınız",
"@todaysWorkout": {},
"description": "Açıklama",
"@description": {},
"name": "İsim",
"@name": {
"description": "Name for a workout or nutritional plan"
},
"ingredient": "Bileşen",
"@ingredient": {},
"save": "Kaydet",
"@save": {},
"addMeal": "Yemek ekle",
"@addMeal": {},
"mealLogged": "Günlüğe kaydedilen yemek",
"@mealLogged": {},
"logMeal": "Bu yemeği kaydet",
"@logMeal": {},
"addIngredient": "Malzeme ekle",
"@addIngredient": {},
"measurements": "Ölçümler",
"@measurements": {
"description": "Categories for the measurements such as biceps size, body fat, etc."
},
"measurement": "Ölçüm",
"@measurement": {},
"searchIngredient": "İçerik ara",
"@searchIngredient": {
"description": "Label on ingredient search form"
},
"nutritionalPlan": "Beslenme planı",
"@nutritionalPlan": {},
"nutritionalDiary": "Beslenme Günlüğü",
"@nutritionalDiary": {},
"noNutritionalPlans": "Beslenme planınız yok",
"@noNutritionalPlans": {
"description": "Message shown when the user has no nutritional plans"
},
"weight": "Ağırlık",
"@weight": {
"description": "The weight of a workout log or body weight entry"
},
"measurementCategoriesHelpText": "\"Pazı\" veya \"vücut yağı\" gibi ölçüm kategorisi",
"@measurementCategoriesHelpText": {},
"measurementEntriesHelpText": "'cm' veya '%' gibi kategoriyi ölçmek için kullanılan birim",
"@measurementEntriesHelpText": {},
"value": "Değer",
"@value": {
"description": "The value of a measurement entry"
},
"start": "Başlangıç",
"@start": {
"description": "Label on button to start the gym mode (i.e., an imperative)"
},
"timeStart": "Başlangıç zamanı",
"@timeStart": {
"description": "The starting time of a workout"
},
"timeEnd": "Bitiş zamanı",
"@timeEnd": {
"description": "The end time of a workout"
},
"timeStartAhead": "Başlangıç zamanı, bitiş zamanından önce olamaz",
"@timeStartAhead": {},
"energy": "Enerji",
"@energy": {
"description": "Energy in a meal, ingredient etc. e.g. in kJ"
},
"kcal": "kilo kalori",
"@kcal": {
"description": "Energy in a meal in kilocalories, kcal"
},
"macronutrients": "Makrobesinler",
"@macronutrients": {},
"planned": "Planlı",
"@planned": {
"description": "Header for the column of 'planned' nutritional values, i.e. what should be eaten"
},
"logged": "Kaydedildi",
"@logged": {
"description": "Header for the column of 'logged' nutritional values, i.e. what was eaten"
},
"difference": "Fark",
"@difference": {},
"percentEnergy": "Enerji Yüzdesi",
"@percentEnergy": {},
"gPerBodyKg": "vücut kg başına düşen g",
"@gPerBodyKg": {
"description": "Label used for total sums of e.g. calories or similar in grams per Kg of body weight"
},
"g": "gram",
"@g": {
"description": "Abbreviation for gram"
},
"protein": "Protein",
"@protein": {},
"proteinShort": "P",
"@proteinShort": {
"description": "The first letter or short name of the word 'Protein', used in overviews"
},
"carbohydrates": "Karbonhidratlar",
"@carbohydrates": {},
"carbohydratesShort": "carb",
"@carbohydratesShort": {
"description": "The first letter or short name of the word 'Carbohydrates', used in overviews"
},
"sugars": "Şekerler",
"@sugars": {},
"fat": "Yağ",
"@fat": {},
"fatShort": "fat",
"@fatShort": {
"description": "The first letter or short name of the word 'Fat', used in overviews"
},
"saturatedFat": "Doymuş yağ",
"@saturatedFat": {},
"fibres": "Lif",
"@fibres": {},
"delete": "Sil",
"@delete": {},
"edit": "Düzenle",
"@edit": {}
}

View File

@@ -109,7 +109,7 @@ class MyApp extends StatelessWidget {
theme: wgerTheme,
home: auth.isAuth
? FutureBuilder(
future: auth.neededApplicationUpdate(),
future: auth.applicationUpdateRequired(),
builder: (ctx, snapshot) =>
snapshot.connectionState == ConnectionState.done && snapshot.data == true
? UpdateAppScreen()

View File

@@ -7,7 +7,10 @@ part of 'weight_entry.dart';
// **************************************************************************
WeightEntry _$WeightEntryFromJson(Map<String, dynamic> json) {
$checkKeys(json, requiredKeys: const ['id', 'weight', 'date']);
$checkKeys(
json,
requiredKeys: const ['id', 'weight', 'date'],
);
return WeightEntry(
id: json['id'] as int?,
weight: stringToNum(json['weight'] as String?),

View File

@@ -7,7 +7,10 @@ part of 'category.dart';
// **************************************************************************
ExerciseCategory _$ExerciseCategoryFromJson(Map<String, dynamic> json) {
$checkKeys(json, requiredKeys: const ['id', 'name']);
$checkKeys(
json,
requiredKeys: const ['id', 'name'],
);
return ExerciseCategory(
id: json['id'] as int,
name: json['name'] as String,

View File

@@ -7,7 +7,10 @@ part of 'equipment.dart';
// **************************************************************************
Equipment _$EquipmentFromJson(Map<String, dynamic> json) {
$checkKeys(json, requiredKeys: const ['id', 'name']);
$checkKeys(
json,
requiredKeys: const ['id', 'name'],
);
return Equipment(
id: json['id'] as int,
name: json['name'] as String,

View File

@@ -7,7 +7,10 @@ part of 'muscle.dart';
// **************************************************************************
Muscle _$MuscleFromJson(Map<String, dynamic> json) {
$checkKeys(json, requiredKeys: const ['id', 'name', 'is_front']);
$checkKeys(
json,
requiredKeys: const ['id', 'name', 'is_front'],
);
return Muscle(
id: json['id'] as int,
name: json['name'] as String,

View File

@@ -7,7 +7,10 @@ part of 'image.dart';
// **************************************************************************
Image _$ImageFromJson(Map<String, dynamic> json) {
$checkKeys(json, requiredKeys: const ['id', 'date', 'image']);
$checkKeys(
json,
requiredKeys: const ['id', 'date', 'image'],
);
return Image(
id: json['id'] as int?,
date: DateTime.parse(json['date'] as String),

View File

@@ -7,7 +7,10 @@ part of 'measurement_category.dart';
// **************************************************************************
MeasurementCategory _$MeasurementCategoryFromJson(Map<String, dynamic> json) {
$checkKeys(json, requiredKeys: const ['id', 'name', 'unit']);
$checkKeys(
json,
requiredKeys: const ['id', 'name', 'unit'],
);
return MeasurementCategory(
id: json['id'] as int?,
name: json['name'] as String,

View File

@@ -7,19 +7,22 @@ part of 'ingredient.dart';
// **************************************************************************
Ingredient _$IngredientFromJson(Map<String, dynamic> json) {
$checkKeys(json, requiredKeys: const [
'id',
'name',
'creation_date',
'energy',
'carbohydrates',
'carbohydrates_sugar',
'protein',
'fat',
'fat_saturated',
'fibres',
'sodium'
]);
$checkKeys(
json,
requiredKeys: const [
'id',
'name',
'creation_date',
'energy',
'carbohydrates',
'carbohydrates_sugar',
'protein',
'fat',
'fat_saturated',
'fibres',
'sodium'
],
);
return Ingredient(
id: json['id'] as int,
name: json['name'] as String,

View File

@@ -6,13 +6,11 @@ part of 'meal.dart';
// JsonSerializableGenerator
// **************************************************************************
Meal _$MealFromJson(Map<String, dynamic> json) {
return Meal(
id: json['id'] as int?,
time: stringToTime(json['time'] as String?),
name: json['name'] as String?,
)..planId = json['plan'] as int;
}
Meal _$MealFromJson(Map<String, dynamic> json) => Meal(
id: json['id'] as int?,
time: stringToTime(json['time'] as String?),
name: json['name'] as String?,
)..planId = json['plan'] as int;
Map<String, dynamic> _$MealToJson(Meal instance) => <String, dynamic>{
'id': instance.id,

View File

@@ -7,7 +7,10 @@ part of 'meal_item.dart';
// **************************************************************************
MealItem _$MealItemFromJson(Map<String, dynamic> json) {
$checkKeys(json, requiredKeys: const ['id', 'amount']);
$checkKeys(
json,
requiredKeys: const ['id', 'amount'],
);
return MealItem(
id: json['id'] as int?,
mealId: json['meal'] as int?,

View File

@@ -7,7 +7,10 @@ part of 'nutritional_plan.dart';
// **************************************************************************
NutritionalPlan _$NutritionalPlanFromJson(Map<String, dynamic> json) {
$checkKeys(json, requiredKeys: const ['id', 'description', 'creation_date']);
$checkKeys(
json,
requiredKeys: const ['id', 'description', 'creation_date'],
);
return NutritionalPlan(
id: json['id'] as int?,
description: json['description'] as String,

View File

@@ -7,7 +7,10 @@ part of 'weight_unit.dart';
// **************************************************************************
WeightUnit _$WeightUnitFromJson(Map<String, dynamic> json) {
$checkKeys(json, requiredKeys: const ['id', 'name']);
$checkKeys(
json,
requiredKeys: const ['id', 'name'],
);
return WeightUnit(
id: json['id'] as int,
name: json['name'] as String,

View File

@@ -7,16 +7,19 @@ part of 'log.dart';
// **************************************************************************
Log _$LogFromJson(Map<String, dynamic> json) {
$checkKeys(json, requiredKeys: const [
'id',
'exercise',
'workout',
'reps',
'repetition_unit',
'weight',
'weight_unit',
'date'
]);
$checkKeys(
json,
requiredKeys: const [
'id',
'exercise',
'workout',
'reps',
'repetition_unit',
'weight',
'weight_unit',
'date'
],
);
return Log(
id: json['id'] as int?,
exerciseId: json['exercise'] as int,

View File

@@ -7,7 +7,10 @@ part of 'repetition_unit.dart';
// **************************************************************************
RepetitionUnit _$RepetitionUnitFromJson(Map<String, dynamic> json) {
$checkKeys(json, requiredKeys: const ['id', 'name']);
$checkKeys(
json,
requiredKeys: const ['id', 'name'],
);
return RepetitionUnit(
id: json['id'] as int,
name: json['name'] as String,

View File

@@ -7,7 +7,10 @@ part of 'set.dart';
// **************************************************************************
Set _$SetFromJson(Map<String, dynamic> json) {
$checkKeys(json, requiredKeys: const ['id', 'sets', 'order', 'comment']);
$checkKeys(
json,
requiredKeys: const ['id', 'sets', 'order', 'comment'],
);
return Set(
day: json['exerciseday'] as int,
sets: json['sets'] as int,

View File

@@ -7,18 +7,21 @@ part of 'setting.dart';
// **************************************************************************
Setting _$SettingFromJson(Map<String, dynamic> json) {
$checkKeys(json, requiredKeys: const [
'id',
'set',
'order',
'exercise',
'repetition_unit',
'reps',
'weight',
'weight_unit',
'comment',
'rir'
]);
$checkKeys(
json,
requiredKeys: const [
'id',
'set',
'order',
'exercise',
'repetition_unit',
'reps',
'weight',
'weight_unit',
'comment',
'rir'
],
);
return Setting(
id: json['id'] as int?,
setId: json['set'] as int,

View File

@@ -7,7 +7,10 @@ part of 'weight_unit.dart';
// **************************************************************************
WeightUnit _$WeightUnitFromJson(Map<String, dynamic> json) {
$checkKeys(json, requiredKeys: const ['id', 'name']);
$checkKeys(
json,
requiredKeys: const ['id', 'name'],
);
return WeightUnit(
id: json['id'] as int,
name: json['name'] as String,

View File

@@ -39,34 +39,38 @@ class AuthProvider with ChangeNotifier {
String? serverUrl;
String? serverVersion;
PackageInfo? applicationVersion;
Map<String, String> metadata = {};
static const MIN_APP_VERSION_URL = 'min-app-version';
static const SERVER_VERSION_URL = 'version';
static const REGISTRATION_URL = 'register';
static const LOGIN_URL = 'login';
late http.Client client;
AuthProvider([http.Client? client, bool? checkMetadata]) {
this.client = client ?? http.Client();
// TODO: this is a workaround since AndroidMetadata doesn't work while running tests
if (checkMetadata ?? true) {
try {
AndroidMetadata.metaDataAsMap.then((value) => metadata = value!);
} on PlatformException {
throw Exception('An error occurred reading the metadata from AndroidManifest');
} catch (error) {}
}
}
/// flag to indicate that the application has successfully loaded all initial data
bool dataInit = false;
// DateTime _expiryDate;
// String _userId;
// Timer _authTimer;
bool get isAuth {
return token != null;
}
String? get token2 {
// if (_expiryDate != null &&
// _expiryDate.isAfter(DateTime.now()) &&
// _token != null) {
return token;
// }
// return null;
}
// String get userId {
// return _userId;
// }
/// Server application version
Future<void> setServerVersion() async {
final response = await http.get(makeUri(serverUrl!, 'version'));
final response = await client.get(makeUri(serverUrl!, SERVER_VERSION_URL));
final responseData = json.decode(response.body);
serverVersion = responseData;
}
@@ -78,15 +82,19 @@ class AuthProvider with ChangeNotifier {
}
/// Checking if there is a new version of the application.
Future<bool> neededApplicationUpdate() async {
if (!ENABLED_UPDATE) {
Future<bool> applicationUpdateRequired([String? version]) async {
if (metadata.containsKey('wger.check_min_app_version') ||
metadata['wger.check_min_app_version'] == 'false') {
return false;
}
final response = await http.get(makeUri(serverUrl!, 'min-app-version'));
final applicationLatestVersion = json.decode(response.body);
final currentVersion = Version.parse(applicationVersion!.version);
final latestAppVersion = Version.parse(applicationLatestVersion);
return latestAppVersion > currentVersion;
final applicationCurrentVersion = version ?? applicationVersion!.version;
final response = await client.get(makeUri(serverUrl!, MIN_APP_VERSION_URL));
final currentVersion = Version.parse(applicationCurrentVersion);
final requiredAppVersion = Version.parse(response.body);
return requiredAppVersion >= currentVersion;
}
/// Registers a new user
@@ -95,27 +103,18 @@ class AuthProvider with ChangeNotifier {
required String password,
required String email,
required String serverUrl}) async {
final uri = Uri.parse('$serverUrl/api/v2/register/');
Map<String, String>? metadata = {};
// Read the api key from the manifest file
try {
metadata = await AndroidMetadata.metaDataAsMap;
} on PlatformException {
throw Exception('An error occurred reading the API key');
}
// Register
try {
final Map<String, String> data = {'username': username, 'password': password};
if (email != '') {
data['email'] = email;
}
final response = await http.post(
uri,
final response = await client.post(
makeUri(serverUrl, REGISTRATION_URL),
headers: {
HttpHeaders.contentTypeHeader: 'application/json; charset=UTF-8',
HttpHeaders.authorizationHeader: "Token ${metadata!['wger.api_key']}"
HttpHeaders.authorizationHeader: 'Token ${metadata[MANIFEST_KEY_API]}',
HttpHeaders.userAgentHeader: getAppNameHeader(),
},
body: json.encode(data),
);
@@ -133,14 +132,14 @@ class AuthProvider with ChangeNotifier {
/// Authenticates a user
Future<void> login(String username, String password, String serverUrl) async {
final uri = Uri.parse('$serverUrl/api/v2/login/');
await logout(shouldNotify: false);
try {
final response = await http.post(
uri,
final response = await client.post(
makeUri(serverUrl, LOGIN_URL),
headers: <String, String>{
HttpHeaders.contentTypeHeader: 'application/json; charset=UTF-8',
HttpHeaders.userAgentHeader: getAppNameHeader(),
},
body: json.encode({'username': username, 'password': password}),
);
@@ -154,13 +153,6 @@ class AuthProvider with ChangeNotifier {
this.serverUrl = serverUrl;
token = responseData['token'];
// _userId = responseData['localId'];
// _expiryDate = DateTime.now().add(
// Duration(
// seconds: int.parse(responseData['expiresIn']),
// ),
// );
notifyListeners();
// store login data in shared preferences
@@ -168,7 +160,6 @@ class AuthProvider with ChangeNotifier {
final userData = json.encode({
'token': token,
'serverUrl': this.serverUrl,
// 'expiryDate': _expiryDate.toIso8601String(),
});
final serverData = json.encode({
'serverUrl': this.serverUrl,
@@ -201,16 +192,9 @@ class AuthProvider with ChangeNotifier {
return false;
}
final extractedUserData = json.decode(prefs.getString('userData')!);
// final expiryDate = DateTime.parse(extractedUserData['expiryDate']);
// if (expiryDate.isBefore(DateTime.now())) {
// return false;
// }
token = extractedUserData['token'];
serverUrl = extractedUserData['serverUrl'];
// _userId = extractedUserData['userId'];
// _expiryDate = expiryDate;
log('autologin successful');
setApplicationVersion();
@@ -225,12 +209,6 @@ class AuthProvider with ChangeNotifier {
token = null;
serverUrl = null;
dataInit = false;
// _userId = null;
// _expiryDate = null;
// if (_authTimer != null) {
// _authTimer.cancel();
// _authTimer = null;
// }
if (shouldNotify) {
notifyListeners();
@@ -240,24 +218,16 @@ class AuthProvider with ChangeNotifier {
prefs.remove('userData');
}
// void _autoLogout() {
// if (_authTimer != null) {
// _authTimer.cancel();
// }
// final timeToExpiry = _expiryDate.difference(DateTime.now()).inSeconds;
// _authTimer = Timer(Duration(seconds: timeToExpiry), logout);
// }
/// Returns the application name and version
///
/// This is used in the headers when talking to the API
String getAppNameHeader() {
String out = '';
if (applicationVersion != null) {
out = '${applicationVersion!.version} '
out = '/${applicationVersion!.version} '
'(${applicationVersion!.packageName}; '
'build: ${applicationVersion!.buildNumber})';
}
return 'wger App/$out';
return 'wger App$out';
}
}

View File

@@ -237,7 +237,7 @@ class ExercisesProvider with ChangeNotifier {
}
Future<void> fetchAndSetEquipment() async {
final equipments = await baseProvider.fetch(baseProvider.makeUrl(_equipmentUrlPath));
final equipments = await fetch(makeUrl(equipmentUrlPath));
try {
for (final equipment in equipments['results']) {
_equipment.add(Equipment.fromJson(equipment));

View File

@@ -334,7 +334,7 @@ class NutritionPlansProvider extends WgerBaseProvider with ChangeNotifier {
Future<void> logMealToDiary(Meal meal) async {
for (final item in meal.mealItems) {
final plan = findById(meal.planId);
final Log log = Log.fromMealItem(item, plan.id!, meal.id!);
final Log log = Log.fromMealItem(item, plan.id!, meal.id);
final data = await post(log.toJson(), makeUrl(_nutritionDiaryPath));
log.id = data['id'];

View File

@@ -16,7 +16,6 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import 'package:android_metadata/android_metadata.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
@@ -119,11 +118,10 @@ class _AuthCardState extends State<AuthCard> {
//
// If not, the user will not be able to register via the app
try {
AndroidMetadata.metaDataAsMap.then((data) {
if (!data!.containsKey('wger.api_key') || data['wger.api_key'] == '') {
_canRegister = false;
}
});
final metadata = Provider.of<AuthProvider>(context, listen: false).metadata;
if (metadata.containsKey(MANIFEST_KEY_API) || metadata[MANIFEST_KEY_API] == '') {
_canRegister = false;
}
} on PlatformException {
_canRegister = false;
}

View File

@@ -130,12 +130,12 @@ class _HomeTabsScreenState extends State<HomeTabsScreen> with SingleTickerProvid
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Center(
const Center(
child: SizedBox(
height: 70,
child: RiveAnimation.asset(
'assets/animations/wger_logo.riv',
animations: const ['idle_loop2'],
animations: ['idle_loop2'],
),
),
),

View File

@@ -53,6 +53,7 @@ class NutritionalPlanScreen extends StatelessWidget {
arguments: FormScreenArguments(
AppLocalizations.of(context).logIngredient,
IngredientLogForm(_nutritionalPlan),
hasListView: true,
),
);
},

View File

@@ -1,58 +0,0 @@
/*
* This file is part of wger Workout Manager <https://github.com/wger-project>.
* Copyright (C) 2020, 2021 wger Team
*
* wger Workout Manager is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* wger Workout Manager is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import 'package:flutter/material.dart';
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
import 'package:provider/provider.dart';
import 'package:wger/providers/auth.dart';
import 'package:wger/providers/body_weight.dart';
import 'package:wger/providers/gallery.dart';
import 'package:wger/providers/nutrition.dart';
import 'package:wger/providers/workout_plans.dart';
import 'package:wger/widgets/core/about.dart';
class AppDrawer extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Drawer(
child: Column(
children: [
AppBar(
title: const Text('wger'),
automaticallyImplyLeading: false,
),
ListTile(
//dense: true,
leading: const Icon(Icons.exit_to_app),
title: Text(AppLocalizations.of(context).logout),
onTap: () {
Provider.of<AuthProvider>(context, listen: false).logout();
Provider.of<WorkoutPlansProvider>(context, listen: false).clear();
Provider.of<NutritionPlansProvider>(context, listen: false).clear();
Provider.of<BodyWeightProvider>(context, listen: false).clear();
Provider.of<GalleryProvider>(context, listen: false).clear();
Navigator.of(context).pop();
Navigator.of(context).pushReplacementNamed('/');
},
),
WgerAboutListTile()
],
),
);
}
}

View File

@@ -210,6 +210,7 @@ class _DashboardNutritionWidgetState extends State<DashboardNutritionWidget> {
arguments: FormScreenArguments(
AppLocalizations.of(context).logIngredient,
IngredientLogForm(_plan!),
hasListView: true,
),
);
},
@@ -351,9 +352,11 @@ class _DashboardWorkoutWidgetState extends State<DashboardWorkoutWidget> {
overflow: TextOverflow.ellipsis,
),
),
MutedText(
day.getDaysText,
textAlign: TextAlign.right,
Expanded(
child: MutedText(
day.getDaysText,
textAlign: TextAlign.right,
),
),
IconButton(
icon: const Icon(Icons.play_arrow),

View File

@@ -226,6 +226,9 @@ class IngredientLogForm extends StatelessWidget {
@override
Widget build(BuildContext context) {
final diaryEntries = _plan.logs;
final String unit = AppLocalizations.of(context).g;
return Container(
margin: const EdgeInsets.all(20),
child: Form(
@@ -295,6 +298,34 @@ class IngredientLogForm extends StatelessWidget {
Navigator.of(context).pop();
},
),
if (diaryEntries.isNotEmpty) const SizedBox(height: 10.0),
Container(
child: Text(AppLocalizations.of(context).recentlyUsedIngredients),
padding: const EdgeInsets.all(10.0),
),
Expanded(
child: ListView.builder(
itemCount: diaryEntries.length,
shrinkWrap: true,
itemBuilder: (context, index) {
return Card(
child: ListTile(
onTap: () {
_ingredientController.text = diaryEntries[index].ingredientObj.name;
_ingredientIdController.text =
diaryEntries[index].ingredientObj.id.toString();
_amountController.text = diaryEntries[index].amount.toStringAsFixed(0);
_mealItem.ingredientId = diaryEntries[index].ingredientId;
_mealItem.amount = diaryEntries[index].amount;
},
title: Text(_plan.logs[index].ingredientObj.name),
subtitle: Text('${diaryEntries[index].amount.toStringAsFixed(0)}$unit'),
trailing: const Icon(Icons.copy),
),
);
},
),
)
],
),
),

View File

@@ -27,7 +27,7 @@ class IngredientTypeahead extends StatefulWidget {
final TextEditingController _ingredientController;
final TextEditingController _ingredientIdController;
IngredientTypeahead(this._ingredientIdController, this._ingredientController);
const IngredientTypeahead(this._ingredientIdController, this._ingredientController);
@override
_IngredientTypeaheadState createState() => _IngredientTypeaheadState();

View File

@@ -98,7 +98,7 @@ class _DayLogWidgetState extends State<DayLogWidget> {
children: [
Text(log.singleLogRepTextNoNl),
IconButton(
icon: Icon(Icons.delete),
icon: const Icon(Icons.delete),
onPressed: () async {
showDeleteDialog(
context, exercise.name, log, exercise, widget._exerciseData);

Some files were not shown because too many files have changed in this diff Show More