Improve group plans support

This commit is contained in:
Phillip Thelen 2022-11-22 15:48:15 +01:00
parent 472a35281f
commit 5049c622dd
115 changed files with 1115 additions and 432 deletions

View file

@ -1,5 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android" android:shape="rectangle">
<solid android:color="@color/window_background" />
<solid android:color="?attr/colorWindowBackground" />
<corners android:radius="12dp" />
</shape>

View file

@ -19,7 +19,7 @@
android:shape="rectangle"
android:layout_width="match_parent"
android:layout_height="match_parent">
<solid android:color="@color/content_background" />
<solid android:color="?attr/colorContentBackground" />
<corners android:radius="6dp" />
</shape>
<ripple android:color="@color/white" />

View file

@ -1,6 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android" android:shape="rectangle">
<size android:width="24dp" android:height="80dp" />
<solid android:color="@color/window_background" />
<solid android:color="?attr/colorWindowBackground" />
<corners android:topRightRadius="8dp" android:bottomRightRadius="8dp" />
</shape>

View file

@ -1,7 +1,7 @@
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">
<solid android:color="@color/content_background" />
<solid android:color="?attr/colorContentBackground" />
<stroke
android:width="0.5dip"

View file

@ -1,5 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android" android:shape="rectangle">
<solid android:color="@color/window_background" />
<solid android:color="?attr/colorWindowBackground" />
<corners android:topLeftRadius="@dimen/bottom_sheet_radius" android:topRightRadius="@dimen/bottom_sheet_radius" />
</shape>

View file

@ -1,6 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">
<solid android:color="@color/window_background" />
<solid android:color="?attr/colorWindowBackground" />
<corners android:topLeftRadius="@dimen/bottom_sheet_radius" android:topRightRadius="@dimen/bottom_sheet_radius" />
</shape>

View file

@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android">
<solid android:color="@color/window_background" />
<solid android:color="?attr/colorWindowBackground" />
<corners android:radius="@dimen/rounded_button_radius"/>
<padding android:left="0dip" android:top="0dip" android:right="0dip" android:bottom="0dip" />
</shape>

View file

@ -6,6 +6,6 @@
android:viewportHeight="24.0">
<path
android:pathData="M0,12l0,12 11.5,-5.7c6.3,-3.2 11.5,-6 11.5,-6.3 0,-0.3 -5.2,-3.1 -11.5,-6.3l-11.5,-5.7 0,12z"
android:strokeColor="@color/content_background"
android:fillColor="@color/content_background"/>
android:strokeColor="?attr/colorContentBackground"
android:fillColor="?attr/colorContentBackground"/>
</vector>

View file

@ -0,0 +1,10 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="20dp"
android:height="20dp"
android:viewportWidth="20"
android:viewportHeight="20"
android:tint="?attr/colorControlNormal">
<path
android:fillColor="@android:color/white"
android:pathData="M16.188,7.542 L12.458,3.812 13.479,2.792Q14,2.271 14.719,2.271Q15.438,2.271 15.958,2.792L17.208,4.042Q17.708,4.542 17.708,5.281Q17.708,6.021 17.208,6.521ZM14.958,8.771 L6.229,17.5H2.5V13.771L11.229,5.042Z"/>
</vector>

View file

@ -3,6 +3,6 @@
<gradient
android:angle="90"
android:endColor="@color/content_80_alpha"
android:startColor="@color/content_background"
android:startColor="?attr/colorContentBackground"
android:type="linear" />
</shape>

View file

@ -2,5 +2,5 @@
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="oval">
<size android:width="24dp" android:height="24dp" />
<stroke android:color="@color/content_background_offset" android:width="2dp" />
<stroke android:color="?attr/colorContentBackgroundOffset" android:width="2dp" />
</shape>

View file

@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android">
<solid android:color="@color/content_background" />
<solid android:color="?attr/colorContentBackground" />
<corners android:radius="@dimen/rounded_button_radius"/>
<padding android:left="0dip" android:top="0dip" android:right="0dip" android:bottom="0dip" />
</shape>

View file

@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android">
<solid android:color="@color/content_background_offset" />
<solid android:color="?attr/colorContentBackgroundOffset" />
<corners android:radius="@dimen/bar_radius"/>
<padding android:left="0dip" android:top="0dip" android:right="0dip" android:bottom="0dip" />
</shape>

View file

@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android">
<solid android:color="@color/window_background" />
<solid android:color="?attr/colorWindowBackground" />
<corners android:radius="6dp"/>
<padding android:left="0dip" android:top="0dip" android:right="0dip" android:bottom="0dip" />
</shape>

View file

@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android">
<solid android:color="@color/content_background" />
<solid android:color="?attr/colorContentBackground" />
<corners android:radius="@dimen/rounded_button_radius"/>
<padding android:left="0dip" android:top="0dip" android:right="0dip" android:bottom="0dip" />
</shape>

View file

@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android">
<solid android:color="@color/window_background" />
<solid android:color="?attr/colorWindowBackground" />
<corners android:radius="@dimen/rounded_button_radius"/>
<padding android:left="0dip" android:top="0dip" android:right="0dip" android:bottom="0dip" />
</shape>

View file

@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android">
<solid android:color="@color/window_background" />
<solid android:color="?attr/colorWindowBackground" />
<corners android:radius="@dimen/bar_radius"/>
<stroke android:width="2dp" android:color="@color/blue_10" />
<padding android:left="0dip" android:top="0dip" android:right="0dip" android:bottom="0dip" />

View file

@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android">
<solid android:color="@color/window_background" />
<solid android:color="?attr/colorWindowBackground" />
<corners android:radius="@dimen/bar_radius"/>
<stroke android:width="2dp" android:color="@color/background_brand" />
<padding android:left="0dip" android:top="0dip" android:right="0dip" android:bottom="0dip" />

View file

@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android">
<solid android:color="@color/window_background" />
<solid android:color="?attr/colorWindowBackground" />
<corners android:radius="@dimen/bar_radius"/>
<stroke android:width="2dp" android:color="@color/red_10" />
<padding android:left="0dip" android:top="0dip" android:right="0dip" android:bottom="0dip" />

View file

@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android">
<solid android:color="@color/window_background" />
<solid android:color="?attr/colorWindowBackground" />
<corners android:radius="@dimen/rounded_button_radius"/>
<padding android:left="0dip" android:top="0dip" android:right="0dip" android:bottom="0dip" />
<stroke android:color="?colorAccent" android:width="2dp" />

View file

@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android">
<solid android:color="@color/window_background" />
<solid android:color="?attr/colorWindowBackground" />
<corners android:radius="@dimen/bar_radius"/>
<stroke android:width="2dp" android:color="@color/yellow_5" />
<padding android:left="0dip" android:top="0dip" android:right="0dip" android:bottom="0dip" />

View file

@ -4,8 +4,8 @@
<gradient
android:angle="90"
android:centerColor="@color/transparent_white"
android:endColor="@color/content_background"
android:startColor="@color/content_background"
android:endColor="?attr/colorContentBackground"
android:startColor="?attr/colorContentBackground"
android:type="linear"
android:centerX="0.6"/>

View file

@ -5,6 +5,6 @@
android:viewportHeight="16">
<path
android:pathData="M0,15v-3l0.7,-0.34 1.05,-0.49 0.95,-0.43 0.84,-0.36 1.27,-0.54 1.07,-0.44 1.57,-0.63 1.28,-0.5 1.37,-0.51 0.96,-0.35 0.99,-0.35 1.03,-0.36 1.06,-0.36L15.25,6l1.7,-0.54 1.17,-0.36 1.2,-0.35 0.61,-0.18 1.24,-0.35 0.63,-0.17 1.28,-0.34c0.43,-0.11 0.87,-0.22 1.3,-0.33l1.33,-0.32a124.57,124.57 0,0 1,4.81 -1.05l1.42,-0.27C37.64,0.69 43.76,0 50,0c4.42,0 9.12,0.33 14.19,0.89l1.8,0.2 1.84,0.22 0.93,0.12 1.88,0.25 1.92,0.26 1.95,0.27 1.99,0.29 3.05,0.45 5.26,0.8 6.63,1.02 4.63,0.7 3.58,0.53 2.44,0.35 2.49,0.35 2.53,0.35 2.58,0.35 1.31,0.17 2.65,0.34c0.89,0.11 1.79,0.22 2.7,0.33l2.74,0.33 2.09,0.24 2.12,0.23 2.15,0.23 1.09,0.11 2.19,0.22 2.22,0.21 2.25,0.21 2.28,0.2 2.31,0.19c0.39,0.03 0.78,0.06 1.17,0.09l2.36,0.18 2.39,0.17 1.21,0.08 2.44,0.15 2.47,0.14 1.25,0.07 2.52,0.13 2.55,0.12c0.43,0.02 0.86,0.04 1.29,0.05l2.6,0.1 2.63,0.09 1.33,0.04 2.69,0.07c1.8,0.04 3.63,0.07 5.48,0.09l2.03,0.02h7.81c12.46,-0.09 24.81,-0.58 36.7,-1.3l3.39,-0.21 1.68,-0.11 3.35,-0.23 3.32,-0.24 1.65,-0.12 3.27,-0.25 4.04,-0.33 5.57,-0.48 3.13,-0.28 4.62,-0.43 4.52,-0.44 2.96,-0.29 4.35,-0.44 4.24,-0.44 5.46,-0.58 17.28,-1.87 3.29,-0.35 3.13,-0.32 2,-0.2 1.92,-0.19 1.84,-0.17 1.77,-0.16 2.09,-0.17 1.97,-0.14 1.48,-0.09c0.64,-0.04 1.25,-0.07 1.84,-0.1l1.14,-0.04 1.07,-0.03A57.3,57.3 0,0 1,316 2c7.31,0 12.87,1.52 16.94,3.38l0.61,0.29 0.59,0.29c0.19,0.1 0.38,0.2 0.57,0.29l0.54,0.3 0.52,0.3 0.5,0.3 0.71,0.45 0.44,0.3 0.42,0.29 0.4,0.29 0.38,0.28 0.53,0.42 0.33,0.27 0.3,0.26 0.28,0.25 0.26,0.24 0.24,0.23 0.33,0.32 0.37,0.38 0.29,0.32 0.33,0.39 0.11,0.14v3.68L174.15,15H0z"
android:fillColor="@color/content_background"
android:fillColor="?attr/colorContentBackground"
android:fillType="evenOdd"/>
</vector>

View file

@ -1,6 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android">
<solid android:color="@color/window_background"/>
<solid android:color="?attr/colorWindowBackground"/>
<corners android:radius="20dip"/>
<padding android:left="0dip" android:top="0dip" android:right="0dip" android:bottom="0dip" />
</shape>

View file

@ -1,6 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android">
<solid android:color="@color/content_background"/>
<solid android:color="?attr/colorContentBackground"/>
<corners android:radius="20dip"/>
<padding android:left="0dip" android:top="0dip" android:right="0dip" android:bottom="0dip" />
</shape>

View file

@ -1,6 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android">
<solid android:color="@color/window_background"/>
<solid android:color="?attr/colorWindowBackground"/>
<corners android:radius="20dip"/>
<padding android:left="0dip" android:top="0dip" android:right="0dip" android:bottom="0dip" />
</shape>

View file

@ -1,7 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android" android:shape="rectangle">
<solid android:color="@color/window_background" />
<solid android:color="?attr/colorWindowBackground" />
<corners android:bottomLeftRadius="4dp" android:bottomRightRadius="4dp" />
</shape>

View file

@ -1,6 +1,6 @@
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">
<solid android:color="@color/content_background" />
<solid android:color="?attr/colorContentBackground" />
<stroke
android:width="1dp"
android:color="@color/offset_background"/>

View file

@ -10,10 +10,10 @@
<size
android:width="10dp"
android:height="10dp" />
<solid android:color="@color/content_background" />
<solid android:color="?attr/colorContentBackground" />
<stroke
android:width="1dp"
android:color="@color/content_background_offset" />
android:color="?attr/colorContentBackgroundOffset" />
</shape>
</rotate>
</item>

View file

@ -1,6 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android">
<solid android:color="@color/window_background"/>
<solid android:color="?attr/colorWindowBackground"/>
<corners android:radius="8dip"/>
<padding android:left="0dip" android:top="0dip" android:right="0dip" android:bottom="0dip" />
</shape>

View file

@ -1,6 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android">
<solid android:color="@color/window_background"/>
<solid android:color="?attr/colorWindowBackground"/>
<corners android:radius="8dip"/>
<padding android:left="0dip" android:top="0dip" android:right="0dip" android:bottom="0dip" />
<stroke android:color="@color/text_brand_neon" android:width="3dp" />

View file

@ -1,5 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android" android:shape="rectangle">
<stroke android:color="@color/content_background_offset" android:width="1dp" />
<stroke android:color="?attr/colorContentBackgroundOffset" android:width="1dp" />
<corners android:radius="4dp" />
</shape>

View file

@ -1,6 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android">
<solid android:color="@color/window_background" />
<solid android:color="?attr/colorWindowBackground" />
<corners android:radius="20dp" />
<stroke android:color="?colorAccent" android:width="2dp" />
</shape>

View file

@ -6,7 +6,7 @@
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context="com.habitrpg.android.habitica.ui.activities.MainActivity"
android:background="@color/content_background">
android:background="?attr/colorContentBackground">
<LinearLayout
android:layout_width="match_parent"
@ -22,7 +22,7 @@
<View
android:layout_width="1dp"
android:layout_height="match_parent"
android:background="@color/window_background"/>
android:background="?attr/colorWindowBackground"/>
<include layout="@layout/activity_main_content" android:id="@+id/content" />
</LinearLayout>
</androidx.drawerlayout.widget.DrawerLayout>

View file

@ -4,7 +4,7 @@
android:layout_height="match_parent"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:background="@color/content_background">
android:background="?attr/colorContentBackground">
<FrameLayout
android:id="@+id/confetti_anchor"
android:layout_width="match_parent"

View file

@ -15,7 +15,7 @@
android:layout_height="wrap_content"
android:minHeight="?attr/actionBarSize"
android:theme="@style/Toolbar"
android:background="@color/content_background"
android:background="?attr/colorContentBackground"
app:layout_scrollFlags="scroll|enterAlways"
app:title="@string/choose_class"
app:titleTextColor="@color/text_primary"

View file

@ -5,7 +5,7 @@
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:background="@color/content_background">
android:background="?attr/colorContentBackground">
<com.google.android.material.appbar.AppBarLayout
android:layout_width="match_parent"
@ -221,7 +221,7 @@
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:nestedScrollingEnabled="false"
android:background="@color/content_background"
android:background="?attr/colorContentBackground"
android:id="@+id/create_challenge_task_list">
</androidx.recyclerview.widget.RecyclerView>

View file

@ -4,7 +4,7 @@
android:layout_height="match_parent"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:background="@color/content_background">
android:background="?attr/colorContentBackground">
<FrameLayout
android:id="@+id/confetti_container"
android:layout_width="match_parent"

View file

@ -30,7 +30,7 @@
android:layout_height="wrap_content"
android:padding="@dimen/outer_inset"
android:text="@string/fix_character_description"
android:background="@color/window_background"
android:background="?attr/colorWindowBackground"
android:layout_marginBottom="16dp"
/>
<FrameLayout
@ -73,7 +73,7 @@
android:layout_width="match_parent"
android:layout_height="2dp"
android:layout_gravity="bottom"
android:background="@color/content_background_offset"
android:background="?attr/colorContentBackgroundOffset"
android:layout_marginEnd="56dp"/>
</FrameLayout>
<FrameLayout
@ -115,7 +115,7 @@
android:layout_width="match_parent"
android:layout_height="2dp"
android:layout_gravity="bottom"
android:background="@color/content_background_offset"
android:background="?attr/colorContentBackgroundOffset"
android:layout_marginEnd="56dp"/>
</FrameLayout>
<FrameLayout
@ -157,7 +157,7 @@
android:layout_width="match_parent"
android:layout_height="2dp"
android:layout_gravity="bottom"
android:background="@color/content_background_offset"
android:background="?attr/colorContentBackgroundOffset"
android:layout_marginEnd="56dp"/>
</FrameLayout>
<FrameLayout
@ -199,7 +199,7 @@
android:layout_width="match_parent"
android:layout_height="2dp"
android:layout_gravity="bottom"
android:background="@color/content_background_offset"
android:background="?attr/colorContentBackgroundOffset"
android:layout_marginEnd="56dp"/>
</FrameLayout>
<FrameLayout
@ -241,7 +241,7 @@
android:layout_width="match_parent"
android:layout_height="2dp"
android:layout_gravity="bottom"
android:background="@color/content_background_offset"
android:background="?attr/colorContentBackgroundOffset"
android:layout_marginEnd="56dp"/>
</FrameLayout>
<FrameLayout
@ -284,7 +284,7 @@
android:layout_width="match_parent"
android:layout_height="2dp"
android:layout_gravity="bottom"
android:background="@color/content_background_offset"
android:background="?attr/colorContentBackgroundOffset"
android:layout_marginEnd="56dp"/>
</FrameLayout>
</LinearLayout>

View file

@ -21,7 +21,7 @@
android:id="@+id/fragment_container"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@color/content_background"
android:background="?attr/colorContentBackground"
app:layout_behavior="@string/appbar_scrolling_view_behavior" />
<com.google.android.material.appbar.AppBarLayout

View file

@ -8,7 +8,7 @@
android:layout_height="match_parent"
tools:context=".ui.activities.PrefsActivity"
android:orientation="vertical"
android:background="@color/content_background">
android:background="?attr/colorContentBackground">
<com.google.android.material.appbar.AppBarLayout
android:layout_width="match_parent"
@ -78,6 +78,6 @@
android:layout_width="match_parent"
android:layout_height="0px"
android:layout_weight="1"
android:background="@color/content_background" />
android:background="?attr/colorContentBackground" />
</LinearLayout>
</FrameLayout>

View file

@ -7,7 +7,7 @@
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context="com.habitrpg.android.habitica.ui.activities.MainActivity"
android:background="@color/content_background">
android:background="?attr/colorContentBackground">
<!-- As the main content view, the view below consumes the entire
space available using match_parent in both dimensions. -->

View file

@ -8,7 +8,7 @@
android:layout_height="match_parent"
tools:context=".ui.activities.PrefsActivity"
android:orientation="vertical"
android:background="@color/content_background">
android:background="?attr/colorContentBackground">
<com.google.android.material.appbar.AppBarLayout
android:layout_width="match_parent"
@ -30,7 +30,7 @@
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
android:layout_gravity="bottom"
android:background="@color/window_background"
android:background="?attr/colorWindowBackground"
android:elevation="0dp"
android:fillViewport="false"
@ -48,7 +48,7 @@
android:layout_width="match_parent"
android:layout_height="0px"
android:layout_weight="1"
android:background="@color/content_background" />
android:background="?attr/colorContentBackground" />
</LinearLayout>

View file

@ -28,7 +28,7 @@
android:minHeight="?attr/actionBarSize"
android:theme="@style/Toolbar"
app:title=""
android:background="@color/content_background"
android:background="?attr/colorContentBackground"
app:layout_scrollFlags="scroll|enterAlways"
app:titleTextColor="@color/text_primary"
app:theme="@style/ReportMessageToolbar"

View file

@ -43,6 +43,6 @@
android:layout_width="match_parent"
android:layout_height="0px"
android:layout_weight="1"
android:background="@color/content_background" />
android:background="?attr/colorContentBackground" />
</LinearLayout>

View file

@ -5,6 +5,7 @@
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:background="?attr/colorTintedBackground"
tools:context="com.habitrpg.android.habitica.ui.activities.FixCharacterValuesActivity">
<com.google.android.material.appbar.AppBarLayout
android:layout_width="match_parent"
@ -29,7 +30,6 @@
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="?attr/colorTintedBackground"
android:paddingBottom="@dimen/spacing_large">
<LinearLayout
android:id="@+id/upper_text_wrapper"
@ -126,10 +126,20 @@
android:layout_height="wrap_content"
android:text="@string/difficulty"
style="@style/TaskFormSectionheader"/>
<com.habitrpg.android.habitica.ui.views.tasks.form.TaskDifficultyButtons
<androidx.compose.ui.platform.ComposeView
android:id="@+id/task_difficulty_buttons"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
<TextView
android:id="@+id/assign_title_view"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/assign"
style="@style/TaskFormSectionheader" />
<androidx.compose.ui.platform.ComposeView
android:id="@+id/assign_view"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
<TextView
android:id="@+id/habit_reset_streak_title_view"
android:layout_width="match_parent"

View file

@ -61,7 +61,7 @@
android:paddingStart="@dimen/spacing_medium"
android:paddingEnd="@dimen/spacing_medium"
android:layout_marginStart="1dp"
android:background="@color/window_background"
android:background="?attr/colorWindowBackground"
android:gravity="center_vertical"
/>
<View android:background="@color/gray_600"
@ -81,7 +81,7 @@
</TableRow>
<TableRow>
<TextView android:text="@string/username"
android:background="@color/window_background"
android:background="?attr/colorWindowBackground"
android:layout_height="41dp"
android:paddingStart="@dimen/spacing_medium"
android:paddingEnd="@dimen/spacing_medium"

View file

@ -36,7 +36,7 @@
android:layout_height="wrap_content"
android:layout_marginBottom="@dimen/header_bar_spacing"
app:barForegroundColor="@color/background_red"
app:barBackgroundColor="@color/window_background"
app:barBackgroundColor="?attr/colorWindowBackground"
app:description="@string/HP_default"/>
<com.habitrpg.common.habitica.views.ValueBar
@ -45,7 +45,7 @@
android:layout_height="wrap_content"
android:layout_marginBottom="@dimen/header_bar_spacing"
app:barForegroundColor="@color/background_yellow"
app:barBackgroundColor="@color/window_background"
app:barBackgroundColor="?attr/colorWindowBackground"
app:description="@string/XP_default"/>
<com.habitrpg.common.habitica.views.ValueBar
@ -53,7 +53,7 @@
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:barForegroundColor="@color/background_blue"
app:barBackgroundColor="@color/window_background"
app:barBackgroundColor="?attr/colorWindowBackground"
app:description="@string/MP_default"/>
</LinearLayout>

View file

@ -3,7 +3,7 @@
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@color/window_background"
android:background="?attr/colorWindowBackground"
android:orientation="vertical"
android:gravity="center">

View file

@ -5,7 +5,7 @@
xmlns:tools="http://schemas.android.com/tools"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:orientation="horizontal"
android:background="@color/content_background"
android:background="?attr/colorContentBackground"
android:paddingStart="@dimen/chat_horizontal_inset"
android:paddingEnd="@dimen/chat_horizontal_inset"
android:paddingTop="@dimen/spacing_small"

View file

@ -113,7 +113,7 @@
android:layout_height="wrap_content"
android:text="@string/close"
android:background="@drawable/alert_dialog_background"
android:backgroundTint="@color/content_background"
android:backgroundTint="?attr/colorContentBackground"
android:layout_marginTop="@dimen/spacing_large"
android:visibility="gone"
tools:visibility="visible"

View file

@ -55,7 +55,7 @@
app:shimmer_repeat_delay="4000"
app:shimmer_duration="200"
app:shimmer_base_color="@color/text_dimmed"
app:shimmer_highlight_color="@color/content_background"
app:shimmer_highlight_color="?attr/colorContentBackground"
app:shimmer_highlight_alpha="0.4">
<com.habitrpg.common.habitica.views.PixelArtView
android:id="@+id/pet_view"

View file

@ -11,7 +11,7 @@
android:layout_height="wrap_content"
android:paddingStart="12dp"
android:paddingEnd="12dp"
android:background="@color/window_background"
android:background="?attr/colorWindowBackground"
android:gravity="center_vertical">
<LinearLayout
android:id="@+id/pin_button"

View file

@ -6,4 +6,4 @@
android:layout_height="wrap_content"
android:orientation="vertical"
android:padding="8dp"
android:background="@color/content_background"/>
android:background="?attr/colorContentBackground"/>

View file

@ -41,6 +41,6 @@
android:layout_width="match_parent"
android:layout_height="2dp"
android:layout_gravity="bottom"
android:background="@color/content_background_offset"
android:background="?attr/colorContentBackgroundOffset"
android:layout_marginEnd="56dp"/>
</merge>

View file

@ -7,7 +7,7 @@
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:background="@color/content_background">
android:background="?attr/colorContentBackground">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
@ -115,7 +115,7 @@
android:paddingEnd="32dp"
android:paddingBottom="32dp"
android:orientation="vertical"
android:background="@color/window_background"
android:background="?attr/colorWindowBackground"
android:layout_marginTop="@dimen/spacing_large"
android:gravity="center">
<TextView

View file

@ -5,7 +5,7 @@
android:layout_height="match_parent"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/refreshLayout"
android:background="@color/content_background">
android:background="?attr/colorContentBackground">
<androidx.core.widget.NestedScrollView
android:layout_width="match_parent"
android:layout_height="match_parent">

View file

@ -10,7 +10,7 @@
android:layout_height="wrap_content"
style="@style/FragmentDialogTitle"
android:id="@+id/titleTextView"
android:background="@color/window_background"
android:background="?attr/colorWindowBackground"
android:textColor="@color/text_primary" />
<androidx.swiperefreshlayout.widget.SwipeRefreshLayout
android:id="@+id/refreshLayout"

View file

@ -13,7 +13,7 @@
android:layout_height="wrap_content"
style="@style/FragmentDialogTitle"
android:id="@+id/titleTextView"
android:background="@color/window_background"
android:background="?attr/colorWindowBackground"
android:textColor="@color/text_primary" />
<com.habitrpg.android.habitica.ui.helpers.RecyclerViewEmptySupport
android:id="@+id/recyclerView"

View file

@ -23,7 +23,7 @@
android:layout_height="63dp"
android:scaleType="fitCenter"
android:padding="6dp"
android:background="@color/window_background"/>
android:background="?attr/colorWindowBackground"/>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"

View file

@ -94,7 +94,7 @@
android:layout_height="wrap_content"
android:paddingStart="@dimen/content_border"
android:paddingEnd="@dimen/content_border"
android:background="@color/window_background"
android:background="?attr/colorWindowBackground"
android:paddingTop="16dp"
android:paddingBottom="16dp"
android:orientation="vertical">

View file

@ -387,7 +387,7 @@
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@color/window_background">
android:background="?attr/colorWindowBackground">
<TextView android:id="@+id/supportTextView"
android:layout_height="wrap_content"
android:layout_width="wrap_content"

View file

@ -6,7 +6,7 @@
<LinearLayout
android:orientation="vertical" android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@color/content_background"
android:background="?attr/colorContentBackground"
android:layout_marginBottom="@dimen/spacing_large">
<TextView
android:layout_width="match_parent"

View file

@ -11,7 +11,7 @@
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:background="@color/content_background"
android:background="?attr/colorContentBackground"
android:paddingTop="@dimen/spacing_large"
android:paddingBottom="@dimen/spacing_large">
<LinearLayout
@ -166,7 +166,7 @@
<View
android:layout_width="match_parent"
android:layout_height="1dp"
android:background="@color/content_background_offset"/>
android:background="?attr/colorContentBackgroundOffset"/>
<Button
android:id="@+id/reset_tutorial_button"
android:layout_width="match_parent"

View file

@ -4,7 +4,7 @@
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@color/window_background"
android:background="?attr/colorWindowBackground"
android:paddingTop="@dimen/spacing_large"
android:paddingBottom="@dimen/spacing_large"
android:paddingStart="@dimen/spacing_large"

View file

@ -4,7 +4,7 @@
android:id="@+id/notification_item"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@color/content_background"
android:background="?attr/colorContentBackground"
android:orientation="vertical"
android:paddingStart="20dp"
android:paddingTop="10dp"

View file

@ -5,7 +5,7 @@
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:parentTag="android.widget.RelativeLayout"
android:background="@color/window_background">
android:background="?attr/colorWindowBackground">
<LinearLayout
android:id="@+id/contentWrapper"
android:layout_width="wrap_content"

View file

@ -5,7 +5,7 @@
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:parentTag="android.widget.RelativeLayout"
android:background="@color/window_background">
android:background="?attr/colorWindowBackground">
<LinearLayout
android:id="@+id/contentWrapper"
android:layout_width="wrap_content"

View file

@ -180,7 +180,7 @@
android:layout_height="wrap_content"
app:title="@string/world_boss_description"
app:identifier="questDescription"
app:color="@color/content_background">
app:color="?attr/colorContentBackground">
<TextView
android:id="@+id/questDescription"
android:layout_width="match_parent"

View file

@ -50,7 +50,7 @@
android:gravity="center_horizontal"
android:textColor="@color/text_secondary"
style="@style/Body1"
app:backgroundTint="@color/content_background_offset"
app:backgroundTint="?attr/colorContentBackgroundOffset"
android:textSize="16sp"
android:inputType="number"/>
</merge>

View file

@ -5,7 +5,7 @@
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:background="@color/content_background">
tools:background="?attr/colorContentBackground">
<ImageView
android:id="@+id/heart_icon"
android:layout_width="20dp"

View file

@ -7,7 +7,7 @@
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@color/content_background">
android:background="?attr/colorContentBackground">
<FrameLayout
android:id="@+id/checkbox_background"

View file

@ -8,7 +8,7 @@
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@color/content_background">
android:background="?attr/colorContentBackground">
<FrameLayout
android:id="@+id/checkbox_background"

View file

@ -1264,6 +1264,9 @@
<string name="positive_and_negative">positive and negative</string>
<string name="assigned_to">Assigned to</string>
<string name="completed_at">Completed at %s</string>
<string name="assign">Assign</string>
<string name="edit_assignees">Edit assignees</string>
<string name="assign_to">Assign to...</string>
<plurals name="you_x_others">
<item quantity="zero">You</item>
<item quantity="one">You, %d other</item>

View file

@ -4,7 +4,7 @@
<style name="AppTheme" parent="Theme.MaterialComponents.DayNight.DarkActionBar">
<item name="android:listSeparatorTextViewStyle">@style/MyOwnListSeperatorTextViewStyle</item>
<item name="android:elevation" tools:targetApi="21">0dp</item>
<item name="android:elevation">0dp</item>
<item name="elevation">0dp</item>
<item name="android:windowContentOverlay">@null</item>
@ -37,6 +37,7 @@
<item name="barColor">@color/brand_300</item>
<item name="colorContentBackground">@color/content_background</item>
<item name="colorContentBackgroundOffset">@color/content_background_offset</item>
<item name="colorWindowBackground">@color/window_background</item>
<item name="colorTintedBackground">@color/brand_800</item>
<item name="colorTintedBackgroundOffset">@color/brand_700</item>
@ -55,8 +56,8 @@
<item name="toolbarContentColor">@color/text_primary</item>
<item name="colorPrimaryText">@color/brand_50</item>
<item name="colorBoxStroke">@color/brand_50</item>
<item name="headerBackgroundColor">@color/content_background</item>
<item name="headerOffsetColor">@color/window_background</item>
<item name="headerBackgroundColor">?attr/colorContentBackground</item>
<item name="headerOffsetColor">?attr/colorWindowBackground</item>
<item name="headerTextColor">@color/text_primary</item>
</style>
@ -311,7 +312,7 @@
<style name="PopupTheme" parent="Widget.AppCompat.PopupMenu.Overflow">
<item name="android:popupBackground">@color/content_background</item>
<item name="android:popupBackground">?attr/colorContentBackground</item>
</style>
<style name="CardText">
@ -595,7 +596,7 @@
</style>
<style name="HabiticaButton.Content" parent="HabiticaButton">
<item name="android:backgroundTint">@color/content_background</item>
<item name="android:backgroundTint">?attr/colorContentBackground</item>
<item name="android:textColor">@color/text_primary</item>
</style>
@ -708,7 +709,7 @@
</style>
<style name="HabiticaButton.Gray" parent="HabiticaButton">
<item name="android:backgroundTint">@color/window_background</item>
<item name="android:backgroundTint">?attr/colorWindowBackground</item>
<item name="android:textColor">@color/text_brand_neon</item>
</style>
@ -975,8 +976,8 @@
</style>
<style name="Widget.App.BottomSheet.Modal" parent="Widget.MaterialComponents.BottomSheet.Modal">
<item name="android:backgroundTint">@color/window_background</item>
<item name="backgroundTint">@color/window_background</item>
<item name="android:backgroundTint">?attr/colorWindowBackground</item>
<item name="backgroundTint">?attr/colorWindowBackground</item>
<item name="android:background">@drawable/bottom_sheet_background</item>
<item name="shapeAppearanceOverlay">@null</item>
<item name="shapeAppearance">@null</item>

View file

@ -162,7 +162,7 @@ class TaskFormActivityTest : ActivityTestCase() {
device.activities.isCurrent(TaskFormActivity::class.java)
textEditText.typeText("New Habit")
KButton { withId(R.id.action_save) }.click()
verify(exactly = 1) { taskRepository.createTaskInBackground(any()) }
verify(exactly = 1) { taskRepository.createTaskInBackground(any(), assignChanges) }
}
}
@ -184,7 +184,7 @@ class TaskFormActivityTest : ActivityTestCase() {
screen {
toolbar {
KView { withId(R.id.action_save) }.click()
verify(exactly = 1) { taskRepository.updateTaskInBackground(any()) }
verify(exactly = 1) { taskRepository.updateTaskInBackground(any(), assignChanges) }
}
}
}
@ -288,7 +288,7 @@ class TaskFormActivityTest : ActivityTestCase() {
bundle.putString(TaskFormActivity.TASK_TYPE_KEY, TaskType.DAILY.value)
bundle.putString(TaskFormActivity.TASK_ID_KEY, task.id!!)
every { taskRepository.getUnmanagedTask(any()) } returns Flowable.just(task)
justRun { taskRepository.updateTaskInBackground(capture(taskSlot)) }
justRun { taskRepository.updateTaskInBackground(capture(taskSlot), assignChanges) }
val intent = Intent(ApplicationProvider.getApplicationContext(), TaskFormActivity::class.java)
intent.putExtras(bundle)
@ -313,7 +313,7 @@ class TaskFormActivityTest : ActivityTestCase() {
typeText("3")
}
KButton { withId(R.id.action_save) }.click()
verify { taskRepository.updateTaskInBackground(any()) }
verify { taskRepository.updateTaskInBackground(any(), assignChanges) }
assert(taskSlot.captured.everyX == 3)
assert(taskSlot.captured.frequency == Frequency.WEEKLY)
}

View file

@ -445,4 +445,10 @@ interface ApiService {
@GET("tasks/group/{groupID}")
suspend fun getTeamPlanTasks(@Path("groupID") groupId: String): HabitResponse<TaskList>
@POST("tasks/{taskID}/assign")
suspend fun assignToTask(@Path("taskID") taskId: String?, @Body ids: List<String>): HabitResponse<Task>
@POST("tasks/{taskID}/unassign/{userID}")
suspend fun unassignFromTask(@Path("taskID") taskID: String, @Path("userID") userID: String): HabitResponse<Task>
}

View file

@ -270,4 +270,6 @@ interface ApiClient {
suspend fun blockMember(userID: String): List<String>?
suspend fun getTeamPlans(): List<TeamPlan>?
suspend fun getTeamPlanTasks(teamID: String): TaskList?
suspend fun assignToTask(taskId: String, ids: List<String>): Task?
suspend fun unassignFromTask(taskId: String, userID: String): Task?
}

View file

@ -53,9 +53,9 @@ interface TaskRepository : BaseRepository {
fun getUnmanagedTask(taskid: String): Flow<Task>
fun updateTaskInBackground(task: Task)
fun updateTaskInBackground(task: Task, assignChanges: Map<String, MutableList<String>>)
fun createTaskInBackground(task: Task)
fun createTaskInBackground(task: Task, assignChanges: Map<String, MutableList<String>>)
fun getTaskCopies(userId: String): Flow<List<Task>>

View file

@ -79,5 +79,5 @@ interface UserRepository : BaseRepository {
suspend fun retrieveTeamPlans(): List<TeamPlan>?
fun getTeamPlans(): Flow<List<TeamPlan>>
suspend fun retrieveTeamPlan(teamID: String): Group?
fun getTeamPlan(teamID: String): Flow<Group>
fun getTeamPlan(teamID: String): Flow<Group?>
}

View file

@ -771,6 +771,14 @@ class ApiClientImpl(
return processResponse(apiService.getTeamPlanTasks(teamID))
}
override suspend fun assignToTask(taskId: String, ids: List<String>): Task? {
return process { apiService.assignToTask(taskId, ids) }
}
override suspend fun unassignFromTask(taskId: String, userID: String): Task? {
return process { apiService.unassignFromTask(taskId, userID) }
}
override suspend fun bulkAllocatePoints(
strength: Int,
intelligence: Int,

View file

@ -294,15 +294,40 @@ class TaskRepositoryImpl(
override fun getUnmanagedTask(taskid: String) = getTask(taskid).map { localRepository.getUnmanagedCopy(it) }
override fun updateTaskInBackground(task: Task) {
override fun updateTaskInBackground(task: Task, assignChanges: Map<String, MutableList<String>>) {
MainScope().launchCatching {
updateTask(task)
val updatedTask = updateTask(task) ?: return@launchCatching
handleAssignmentChanges(updatedTask, assignChanges)
}
}
override fun createTaskInBackground(task: Task) {
override fun createTaskInBackground(task: Task, assignChanges: Map<String, MutableList<String>>) {
MainScope().launchCatching {
createTask(task)
val createdTask = createTask(task) ?: return@launchCatching
handleAssignmentChanges(createdTask, assignChanges)
}
}
private suspend fun handleAssignmentChanges(task: Task, assignChanges: Map<String, MutableList<String>>) {
val taskID = task.id ?: return
assignChanges["assign"]?.let { assignments ->
if (assignments.isEmpty()) return@let
val savedTask = apiClient.assignToTask(taskID, assignments) ?: return@let
savedTask.id = task.id
savedTask.position = task.position
localRepository.save(task)
}
assignChanges["unassign"]?.let { unassignments ->
var savedTask: Task? = null
for (unassignment in unassignments) {
savedTask = apiClient.unassignFromTask(taskID, unassignment)
}
if (savedTask != null) {
savedTask.id = task.id
savedTask.position = task.position
localRepository.save(task)
}
}
}

View file

@ -22,6 +22,7 @@ import com.habitrpg.shared.habitica.models.responses.TaskDirection
import com.habitrpg.shared.habitica.models.tasks.Attribute
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.firstOrNull
import kotlinx.coroutines.flow.map
import java.util.Date
import java.util.GregorianCalendar
import java.util.concurrent.TimeUnit
@ -366,8 +367,11 @@ class UserRepositoryImpl(
return team
}
override fun getTeamPlan(teamID: String): Flow<Group> {
override fun getTeamPlan(teamID: String): Flow<Group?> {
return localRepository.getTeamPlan(teamID)
.map {
it ?: retrieveTeamPlan(teamID)
}
}
private suspend fun getLiveUser(): User? {

View file

@ -28,5 +28,5 @@ interface UserLocalRepository : BaseLocalRepository {
fun getQuestAchievements(userID: String): Flow<List<QuestAchievement>>
fun getUserQuestStatus(userID: String): Flow<UserQuestStatus>
fun getTeamPlans(userID: String): Flow<List<TeamPlan>>
fun getTeamPlan(teamID: String): Flow<Group>
fun getTeamPlan(teamID: String): Flow<Group?>
}

View file

@ -124,15 +124,14 @@ class RealmUserLocalRepository(realm: Realm) : RealmBaseLocalRepository(realm),
.filter { it.isLoaded }
}
override fun getTeamPlan(teamID: String): Flow<Group> {
override fun getTeamPlan(teamID: String): Flow<Group?> {
if (realm.isClosed) return emptyFlow()
return realm.where(Group::class.java)
.equalTo("id", teamID)
.findAll()
.toFlow()
.filter { realmObject -> realmObject.isLoaded && realmObject.isValid && !realmObject.isEmpty() }
.filter { realmObject -> realmObject.isLoaded && realmObject.isValid }
.map { teams -> teams.firstOrNull() }
.filterNotNull()
}
override fun getSkills(user: User): Flow<List<Skill>> {

View file

@ -6,5 +6,6 @@ import com.habitrpg.android.habitica.models.tasks.Task
interface GroupPlanInfoProvider {
fun assignedTextForTask(resources: Resources, assignedUsers: List<String>): String
fun canScoreTask(task: Task): Boolean
fun canEditTask(task: Task): Boolean
suspend fun canEditTask(task: Task): Boolean
suspend fun canAddTasks(): Boolean
}

View file

@ -3,6 +3,8 @@ package com.habitrpg.android.habitica.helpers
import android.content.Context
import com.habitrpg.android.habitica.R
import com.habitrpg.android.habitica.models.tasks.Task
import com.habitrpg.common.habitica.extensions.nameRes
import com.habitrpg.shared.habitica.models.tasks.TaskDifficulty
import com.habitrpg.shared.habitica.models.tasks.TaskType
import java.text.DateFormat
import java.util.Date
@ -41,12 +43,6 @@ class TaskDescriptionBuilder(private val context: Context) {
}
private fun describeDifficulty(difficulty: Float): String {
return when (difficulty) {
0.1f -> context.getString(R.string.trivial)
1.0f -> context.getString(R.string.easy)
1.5f -> context.getString(R.string.medium)
2.0f -> context.getString(R.string.hard)
else -> ""
}
return context.getString(TaskDifficulty.valueOf(difficulty).nameRes)
}
}

View file

@ -0,0 +1,9 @@
package com.habitrpg.android.habitica.models
import com.habitrpg.shared.habitica.models.Avatar
interface Assignable {
val id: String?
val avatar: Avatar?
val identifiableName: String
}

View file

@ -1,6 +1,7 @@
package com.habitrpg.android.habitica.models.members
import com.google.gson.annotations.SerializedName
import com.habitrpg.android.habitica.models.Assignable
import com.habitrpg.android.habitica.models.BaseMainObject
import com.habitrpg.android.habitica.models.social.UserParty
import com.habitrpg.android.habitica.models.user.Authentication
@ -16,11 +17,11 @@ import io.realm.RealmModel
import io.realm.RealmObject
import io.realm.annotations.PrimaryKey
open class Member : RealmObject(), Avatar, BaseMainObject {
open class Member : RealmObject(), Avatar, BaseMainObject, Assignable {
@PrimaryKey
@SerializedName("_id")
var id: String? = null
override var id: String? = null
override var stats: Stats? = null
var inbox: Inbox? = null
override var preferences: MemberPreferences? = null
@ -50,6 +51,12 @@ open class Member : RealmObject(), Avatar, BaseMainObject {
""
} else this.profile?.name ?: ""
override val identifiableName: String
get() = username ?: ""
override val avatar: Avatar
get() = this
val petsFoundCount: Int
get() = this.items?.pets?.size ?: 0
val mountsTamedCount: Int

View file

@ -3,7 +3,6 @@ package com.habitrpg.android.habitica.models.social
import com.google.gson.annotations.SerializedName
import com.habitrpg.android.habitica.models.BaseMainObject
import com.habitrpg.android.habitica.models.inventory.Quest
import com.habitrpg.android.habitica.models.tasks.TaskList
import com.habitrpg.shared.habitica.models.tasks.TasksOrder
import io.realm.RealmList
import io.realm.RealmObject
@ -27,6 +26,7 @@ open class Group : RealmObject(), BaseMainObject {
var summary: String? = null
var leaderID: String? = null
var leaderName: String? = null
var managers: RealmList<String>? = null
var name: String? = null
var memberCount: Int = 0
var type: String? = null
@ -54,6 +54,13 @@ open class Group : RealmObject(), BaseMainObject {
return id.hashCode()
}
fun hasTaskEditPrivileges(userID: String): Boolean {
if (leaderID == userID) {
return true
}
return managers?.contains(userID) == true
}
companion object {
const val TAVERN_ID = "00000000-0000-4000-A000-000000000000"
}

View file

@ -63,6 +63,7 @@ class UserRepositoryModule {
}
@Provides
@UserScope
fun providesTaskRepository(
localRepository: TaskLocalRepository,
apiClient: ApiClient,

View file

@ -100,9 +100,9 @@ class FullProfileActivity : BaseActivity() {
R.color.transparent
)
)
binding.avatarWithBars.hpBar.barBackgroundColor = getThemeColor(R.color.window_background)
binding.avatarWithBars.xpBar.barBackgroundColor = getThemeColor(R.color.window_background)
binding.avatarWithBars.mpBar.barBackgroundColor = getThemeColor(R.color.window_background)
binding.avatarWithBars.hpBar.barBackgroundColor = getThemeColor(R.attr.colorWindowBackground)
binding.avatarWithBars.xpBar.barBackgroundColor = getThemeColor(R.attr.colorWindowBackground)
binding.avatarWithBars.mpBar.barBackgroundColor = getThemeColor(R.attr.colorWindowBackground)
attributeRows.clear()
binding.attributesCardView.setOnClickListener { toggleAttributeDetails() }

View file

@ -18,6 +18,48 @@ import android.view.WindowManager
import android.widget.CheckBox
import android.widget.TextView
import androidx.appcompat.widget.AppCompatCheckBox
import androidx.compose.animation.animateColor
import androidx.compose.animation.core.FastOutLinearInEasing
import androidx.compose.animation.core.Spring
import androidx.compose.animation.core.animateFloat
import androidx.compose.animation.core.spring
import androidx.compose.animation.core.tween
import androidx.compose.animation.core.updateTransition
import androidx.compose.animation.fadeIn
import androidx.compose.foundation.Image
import androidx.compose.foundation.background
import androidx.compose.foundation.border
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.heightIn
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.ButtonDefaults
import androidx.compose.material.MaterialTheme
import androidx.compose.material.Text
import androidx.compose.material.TextButton
import androidx.compose.runtime.Composable
import androidx.compose.runtime.mutableStateListOf
import androidx.compose.runtime.mutableStateMapOf
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.toMutableStateList
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.rotate
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.ColorFilter
import androidx.compose.ui.res.colorResource
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import androidx.core.content.ContextCompat
import androidx.core.os.bundleOf
import androidx.core.view.children
@ -27,6 +69,7 @@ import androidx.lifecycle.lifecycleScope
import com.habitrpg.android.habitica.R
import com.habitrpg.android.habitica.components.UserComponent
import com.habitrpg.android.habitica.data.ChallengeRepository
import com.habitrpg.android.habitica.data.SocialRepository
import com.habitrpg.android.habitica.data.TagRepository
import com.habitrpg.android.habitica.data.TaskRepository
import com.habitrpg.android.habitica.databinding.ActivityTaskFormBinding
@ -35,17 +78,26 @@ import com.habitrpg.android.habitica.extensions.addCancelButton
import com.habitrpg.android.habitica.helpers.ExceptionHandler
import com.habitrpg.android.habitica.helpers.TaskAlarmManager
import com.habitrpg.android.habitica.helpers.launchCatching
import com.habitrpg.android.habitica.models.Assignable
import com.habitrpg.android.habitica.models.Tag
import com.habitrpg.android.habitica.models.members.Member
import com.habitrpg.android.habitica.models.social.Challenge
import com.habitrpg.android.habitica.models.tasks.Task
import com.habitrpg.android.habitica.models.tasks.TaskGroupPlan
import com.habitrpg.android.habitica.ui.helpers.dismissKeyboard
import com.habitrpg.android.habitica.ui.theme.HabiticaTheme
import com.habitrpg.android.habitica.ui.viewmodels.MainUserViewModel
import com.habitrpg.android.habitica.ui.views.CompletedAt
import com.habitrpg.android.habitica.ui.views.UserRow
import com.habitrpg.android.habitica.ui.views.dialogs.HabiticaAlertDialog
import com.habitrpg.android.habitica.ui.views.showAsBottomSheet
import com.habitrpg.android.habitica.ui.views.tasks.form.TaskDifficultySelector
import com.habitrpg.common.habitica.extensions.dpToPx
import com.habitrpg.common.habitica.extensions.getThemeColor
import com.habitrpg.shared.habitica.models.tasks.Attribute
import com.habitrpg.shared.habitica.models.tasks.Frequency
import com.habitrpg.shared.habitica.models.tasks.HabitResetOption
import com.habitrpg.shared.habitica.models.tasks.TaskDifficulty
import com.habitrpg.shared.habitica.models.tasks.TaskType
import io.realm.RealmList
import kotlinx.coroutines.flow.firstOrNull
@ -78,6 +130,9 @@ class TaskFormActivity : BaseActivity() {
@Inject
lateinit var userViewModel: MainUserViewModel
@Inject
lateinit var socialRepository: SocialRepository
private var challenge: Challenge? = null
private var isCreating = true
@ -87,6 +142,10 @@ class TaskFormActivity : BaseActivity() {
private var initialTaskInstance: Task? = null
private var taskType: TaskType = TaskType.HABIT
private var tags = listOf<Tag>()
private var groupID: String? = null
private var groupMembers = listOf<Member>()
private var assignedIDs = mutableStateListOf<String>()
private var taskCompletedMap = mutableStateMapOf<String, Date>()
private var preselectedTags: ArrayList<String>? = null
private var hasPreselectedTags = false
private var selectedStat = Attribute.STRENGTH
@ -98,10 +157,11 @@ class TaskFormActivity : BaseActivity() {
private var isDiscardCancelled: Boolean = false
private var canSave: Boolean = false
private var taskDifficulty = mutableStateOf(TaskDifficulty.EASY)
private var tintColor: Int = 0
set(value) {
field = value
binding.taskDifficultyButtons.tintColor = value
binding.habitScoringButtons.tintColor = value
binding.habitResetStreakButtons.tintColor = value
binding.taskSchedulingControls.tintColor = value
@ -126,6 +186,7 @@ class TaskFormActivity : BaseActivity() {
val bundle = intent.extras ?: return
val taskId = bundle.getString(TASK_ID_KEY)
groupID = bundle.getString(GROUP_ID_KEY)
forcedIsNight = false
forcedTheme = if (taskId != null) {
val taskValue = bundle.getDouble(TASK_VALUE_KEY)
@ -144,13 +205,9 @@ class TaskFormActivity : BaseActivity() {
super.onCreate(savedInstanceState)
if (forcedTheme == "yellow") {
binding.taskDifficultyButtons.textTintColor =
ContextCompat.getColor(this, R.color.text_yellow)
binding.habitScoringButtons.textTintColor =
ContextCompat.getColor(this, R.color.text_yellow)
} else if (forcedTheme == "taskform") {
binding.taskDifficultyButtons.textTintColor =
ContextCompat.getColor(this, R.color.text_brand_neon)
binding.habitScoringButtons.textTintColor =
ContextCompat.getColor(this, R.color.text_brand_neon)
}
@ -210,6 +267,32 @@ class TaskFormActivity : BaseActivity() {
}
}
if (groupID != null) {
binding.assignView.setContent {
HabiticaTheme {
AssignedView(
groupMembers.filter { assignedIDs.contains(it.id) },
taskCompletedMap,
task?.extraExtraLightTaskColor?.let { colorResource(it) } ?: Color(getThemeColor(R.attr.colorTintedBackgroundOffset)),
colorResource(task?.darkestTaskColor ?: R.color.text_primary),
{
showAssignDialog()
},
showEditButton = true
)
}
}
lifecycleScope.launchCatching {
socialRepository.getGroupMembers(groupID ?: "").collect {
groupMembers = it
}
}
} else {
binding.assignTitleView.visibility = View.GONE
binding.assignView.visibility = View.GONE
}
title = ""
when {
taskId != null -> {
@ -237,10 +320,11 @@ class TaskFormActivity : BaseActivity() {
}
bundle.containsKey(PARCELABLE_TASK) -> {
isCreating = false
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
task = bundle.getParcelable(PARCELABLE_TASK, Task::class.java)
task = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
bundle.getParcelable(PARCELABLE_TASK, Task::class.java)
} else {
task = bundle.getParcelable(PARCELABLE_TASK)
@Suppress("DEPRECATION")
bundle.getParcelable(PARCELABLE_TASK)
}
task?.let { fillForm(it) }
}
@ -259,6 +343,13 @@ class TaskFormActivity : BaseActivity() {
initialTaskInstance = configureTask(Task())
}
}
binding.taskDifficultyButtons.setContent {
HabiticaTheme {
TaskDifficultySelector(taskDifficulty.value, onSelect = { taskDifficulty.value = it })
}
}
configureForm()
}
@ -385,6 +476,11 @@ class TaskFormActivity : BaseActivity() {
if (isCreating) {
binding.adjustStreakTitleView.visibility = View.GONE
binding.adjustStreakWrapper.visibility = View.GONE
if (groupID != null) {
binding.habitResetStreakTitleView.visibility = View.GONE
binding.habitResetStreakButtons.visibility = View.GONE
}
}
}
@ -423,7 +519,7 @@ class TaskFormActivity : BaseActivity() {
canSave = true
binding.textEditText.setText(task.text)
binding.notesEditText.setText(task.notes)
binding.taskDifficultyButtons.selectedDifficulty = task.priority
taskDifficulty.value = TaskDifficulty.valueOf(task.priority)
when (taskType) {
TaskType.HABIT -> {
binding.habitScoringButtons.isPositive = task.up ?: false
@ -461,6 +557,22 @@ class TaskFormActivity : BaseActivity() {
task.reminders?.let { binding.remindersContainer.reminders = it }
}
task.attribute?.let { selectedStat = it }
if (task.isGroupTask) {
(binding.habitAdjustPositiveStreakView.parent as ViewGroup).visibility = View.GONE
(binding.habitAdjustNegativeStreakView.parent as ViewGroup).visibility = View.GONE
binding.statWrapper.visibility = View.GONE
binding.habitResetStreakTitleView.visibility = View.GONE
binding.habitResetStreakButtons.visibility = View.GONE
assignedIDs = task.group?.assignedUsersDetail?.map { it.assignedUserID }?.filterNotNull()?.toMutableStateList() ?: mutableStateListOf()
task.group?.assignedUsersDetail?.forEach {
it.completedDate?.let { date ->
taskCompletedMap[it.assignedUserID ?: ""] = date
}
}
}
setAllTagSelections()
}
@ -511,7 +623,7 @@ class TaskFormActivity : BaseActivity() {
thisTask.text = binding.textEditText.text.toString()
thisTask.notes = binding.notesEditText.text.toString()
thisTask.priority = binding.taskDifficultyButtons.selectedDifficulty
thisTask.priority = taskDifficulty.value.value
if (usesTaskAttributeStats) {
thisTask.attribute = selectedStat
}
@ -553,6 +665,13 @@ class TaskFormActivity : BaseActivity() {
}
}
if (groupID != null) {
if (thisTask.group?.groupID != groupID) {
thisTask.group = TaskGroupPlan()
thisTask.group?.groupID = groupID
}
}
return thisTask
}
@ -574,11 +693,29 @@ class TaskFormActivity : BaseActivity() {
val resultIntent = Intent()
resultIntent.putExtra(TASK_TYPE_KEY, taskType.value)
val assignChanges = mapOf(
"assign" to mutableListOf<String>(),
"unassign" to mutableListOf<String>()
)
if (thisTask.isGroupTask) {
for (id in assignedIDs) {
if (thisTask.group?.assignedUsersDetail?.firstOrNull { it.assignedUserID == id } == null) {
assignChanges["assign"]?.add(id)
}
}
for (details in thisTask.group?.assignedUsersDetail ?: listOf()) {
if (assignedIDs.firstOrNull { it == details.assignedUserID } == null) {
details.assignedUserID?.let { assignChanges["unassign"]?.add(it) }
}
}
}
if (!isChallengeTask) {
if (isCreating) {
taskRepository.createTaskInBackground(thisTask)
taskRepository.createTaskInBackground(thisTask, assignChanges)
} else {
taskRepository.updateTaskInBackground(thisTask)
taskRepository.updateTaskInBackground(thisTask, assignChanges)
}
if (isDiscardCancelled) {
analyticsManager.logEvent("back_to_task", bundleOf(Pair("is_creating", isCreating)))
@ -626,7 +763,8 @@ class TaskFormActivity : BaseActivity() {
private fun showChallengeDeleteTask() {
lifecycleScope.launchCatching {
val tasks = taskRepository.getTasksForChallenge(task?.challengeID).firstOrNull() ?: return@launchCatching
val tasks = taskRepository.getTasksForChallenge(task?.challengeID).firstOrNull()
?: return@launchCatching
val taskCount = tasks.size
val alert = HabiticaAlertDialog(this@TaskFormActivity)
alert.setTitle(getString(R.string.delete_challenge_task_title))
@ -673,39 +811,40 @@ class TaskFormActivity : BaseActivity() {
return
}
lifecycleScope.launchCatching {
val tasks = taskRepository.getTasksForChallenge(task.challengeID).firstOrNull() ?: return@launchCatching
val taskCount = tasks.size
val dialog = HabiticaAlertDialog(this@TaskFormActivity)
dialog.setTitle(R.string.broken_challenge)
dialog.setMessage(
getString(
R.string.broken_challenge_description,
taskCount
)
)
dialog.addButton(
getString(R.string.keep_x_tasks, taskCount),
true
) { _, _ ->
lifecycleScope.launchCatching {
taskRepository.unlinkAllTasks(task.challengeID, "keep-all")
userRepository.retrieveUser(true, true)
}
}
dialog.addButton(
getString(R.string.delete_x_tasks, taskCount),
false,
true
) { _, _ ->
lifecycleScope.launchCatching {
val tasks = taskRepository.getTasksForChallenge(task.challengeID).firstOrNull()
?: return@launchCatching
val taskCount = tasks.size
val dialog = HabiticaAlertDialog(this@TaskFormActivity)
dialog.setTitle(R.string.broken_challenge)
dialog.setMessage(
getString(
R.string.broken_challenge_description,
taskCount
)
)
dialog.addButton(
getString(R.string.keep_x_tasks, taskCount),
true
) { _, _ ->
lifecycleScope.launchCatching {
taskRepository.unlinkAllTasks(task.challengeID, "keep-all")
userRepository.retrieveUser(true, true)
}
}
dialog.addButton(
getString(R.string.delete_x_tasks, taskCount),
false,
true
) { _, _ ->
lifecycleScope.launchCatching {
taskRepository.unlinkAllTasks(task.challengeID, "remove-all")
userRepository.retrieveUser(true, true)
}
}
dialog.setExtraCloseButtonVisibility(View.VISIBLE)
dialog.show()
}
taskRepository.unlinkAllTasks(task.challengeID, "remove-all")
userRepository.retrieveUser(true, true)
}
}
dialog.setExtraCloseButtonVisibility(View.VISIBLE)
dialog.show()
}
}
private fun disableEditingForUneditableFieldsInChallengeTask() {
@ -720,9 +859,26 @@ class TaskFormActivity : BaseActivity() {
super.finish()
}
private fun showAssignDialog() {
showAsBottomSheet {
AssignSheet(
groupMembers,
assignedIDs,
{
if (assignedIDs.contains(it)) {
assignedIDs.remove(it)
} else {
assignedIDs.add(it)
}
}
)
}
}
companion object {
const val SELECTED_TAGS_KEY = "selectedTags"
const val TASK_ID_KEY = "taskId"
const val GROUP_ID_KEY = "groupId"
const val TASK_VALUE_KEY = "taskValue"
const val USER_ID_KEY = "userId"
const val TASK_TYPE_KEY = "type"
@ -742,3 +898,142 @@ private fun String.toIntCatchOverflow(): Int? {
0
}
}
@Composable
fun AssignedView(
assigned: List<Assignable>,
completedAt: Map<String, Date>,
backgroundColor: Color,
color: Color,
onEditClick: () -> Unit,
modifier: Modifier = Modifier,
showEditButton: Boolean = false
) {
Column(modifier.fillMaxWidth()) {
val rowModifier = Modifier
.padding(vertical = 4.dp)
.background(
backgroundColor,
RoundedCornerShape(8.dp)
)
.padding(15.dp, 12.dp)
.heightIn(min = 24.dp)
.fillMaxWidth()
for (assignable in assigned) {
UserRow(
username = assignable.identifiableName, modifier = rowModifier,
color = color,
extraContent = {
completedAt[assignable.id]?.let { CompletedAt(completedAt = it) }
}
)
}
if (showEditButton) {
Row(verticalAlignment = Alignment.CenterVertically, modifier = Modifier
.clickable {
onEditClick()
}
.padding(vertical = 4.dp)
.background(
backgroundColor,
RoundedCornerShape(8.dp)
)
.padding(15.dp, 12.dp)
.heightIn(min = 24.dp)
.fillMaxWidth()) {
Image(
painterResource(R.drawable.edit),
null,
colorFilter = ColorFilter.tint(MaterialTheme.colors.primary)
)
Text(stringResource(R.string.edit_assignees), color = color,
modifier = Modifier.padding(start = 4.dp))
}
}
}
}
@Composable
fun AssignSheet(
members: List<Member>,
assignedMembers: List<String>,
onAssignClick: (String) -> Unit,
modifier: Modifier = Modifier
) {
Column(modifier) {
Box {
Text(
stringResource(R.string.assign_to),
fontSize = 16.sp,
fontWeight = FontWeight.Medium,
color = colorResource(R.color.gray_200),
textAlign = TextAlign.Center,
modifier = Modifier
.fillMaxWidth()
.padding(top = 8.dp)
)
TextButton(
onClick = {
},
colors = ButtonDefaults.textButtonColors(),
modifier = Modifier.align(Alignment.CenterEnd)
) {
Text(stringResource(R.string.done))
}
}
for (member in members) {
val isAssigned = assignedMembers.contains(member.id)
val transition = updateTransition(isAssigned, label = "isAssigned")
val rotation = transition.animateFloat(label = "isAssigned", transitionSpec = { spring(Spring.DampingRatioLowBouncy, Spring.StiffnessLow) }) {
if (it) 0f else 45f
}
val backgroundColor = transition.animateColor(label = "isAssigned", transitionSpec = { tween(400, easing = FastOutLinearInEasing) }) {
if (it) MaterialTheme.colors.primary else colorResource(id = R.color.transparent)
}
val color = transition.animateColor(label = "isAssigned", transitionSpec = { tween(400, easing = FastOutLinearInEasing) }) {
fadeIn(tween(10000))
colorResource(if (it) R.color.white else R.color.text_dimmed)
}
val borderColor = transition.animateColor(label = "isAssigned", transitionSpec = { tween(400, easing = FastOutLinearInEasing) }) {
fadeIn(tween(10000))
if (it) MaterialTheme.colors.primary else colorResource(id = R.color.text_dimmed)
}
UserRow(
username = member.displayName,
color = colorResource(R.color.text_primary),
extraContent = {
Text(
member.formattedUsername ?: "",
color = colorResource(R.color.text_ternary)
)
}, endContent = {
Image(
painterResource(R.drawable.ic_close_white_24dp),
null,
colorFilter = ColorFilter.tint(color.value),
modifier = Modifier
.rotate(rotation.value)
.size(24.dp)
.background(
backgroundColor.value,
CircleShape
)
.border(
2.dp,
borderColor.value,
CircleShape
)
.padding(3.dp)
)
}, modifier = Modifier
.clickable {
member.id?.let { onAssignClick(it) }
}
.padding(30.dp, 12.dp)
.heightIn(min = 24.dp)
.fillMaxWidth()
)
}
}
}

View file

@ -14,7 +14,6 @@ import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.Button
import androidx.compose.material.ButtonDefaults
import androidx.compose.material.MaterialTheme
import androidx.compose.material.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.DisposableEffect
@ -23,7 +22,6 @@ import androidx.compose.runtime.livedata.observeAsState
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.shadow
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.ColorFilter
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.colorResource
@ -48,8 +46,9 @@ import com.habitrpg.android.habitica.helpers.MainNavigationController
import com.habitrpg.android.habitica.helpers.TaskDescriptionBuilder
import com.habitrpg.android.habitica.ui.theme.HabiticaTheme
import com.habitrpg.android.habitica.ui.viewmodels.BaseViewModel
import com.habitrpg.android.habitica.ui.views.CompletedAt
import com.habitrpg.android.habitica.ui.views.UserRow
import com.habitrpg.shared.habitica.models.tasks.TaskType
import java.text.DateFormat
import javax.inject.Inject
class TaskSummaryViewModel(val taskId: String) : BaseViewModel() {
@ -100,7 +99,6 @@ fun TaskSummaryView(viewModel: TaskSummaryViewModel) {
val task by viewModel.task.observeAsState()
val titleModifier = Modifier.padding(top = 30.dp)
val textModifier = Modifier.padding(top = 4.dp)
val completedTimeFormat = DateFormat.getTimeInstance(DateFormat.SHORT)
if (task != null) {
val darkestColor = colorResource(task?.darkestTaskColor ?: R.color.text_primary)
val systemUiController = rememberSystemUiController()
@ -245,18 +243,7 @@ fun TaskSummaryView(viewModel: TaskSummaryViewModel) {
.fillMaxWidth(),
color = darkestColor,
extraContent = if (item.completed) ({
Row(
verticalAlignment = Alignment.CenterVertically,
modifier = Modifier.padding(top = 4.dp)
) {
Image(painterResource(R.drawable.completed), null)
Text(stringResource(R.string.completed_at,
item.completedDate?.let { completedTimeFormat.format(it) }
?: ""),
fontSize = 14.sp,
color = colorResource(R.color.green_10),
modifier = Modifier.padding(start = 4.dp))
}
CompletedAt(item.completedDate)
}) else null
)
}
@ -281,26 +268,6 @@ private fun String.makeBoldComposable(): AnnotatedString {
}
}
@Composable
fun UserRow(
username: String,
modifier: Modifier = Modifier,
extraContent: @Composable (() -> Unit)? = null,
color: Color? = null
) {
Row(verticalAlignment = Alignment.CenterVertically, modifier = modifier) {
Column {
Text(
"@$username", fontSize = 16.sp, fontWeight = FontWeight.Medium, color = color ?: MaterialTheme.colors.primary, modifier = Modifier
.fillMaxWidth()
)
if (extraContent != null) {
extraContent()
}
}
}
}
@Preview
@Composable
private fun TaskSummaryViewPreview() {

View file

@ -50,6 +50,7 @@ import com.habitrpg.common.habitica.helpers.EmptyItem
import com.habitrpg.shared.habitica.models.responses.TaskDirection
import com.habitrpg.shared.habitica.models.responses.TaskScoringResult
import com.habitrpg.shared.habitica.models.tasks.TaskType
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.distinctUntilChangedBy
@ -58,6 +59,7 @@ import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.flow.takeWhile
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import java.util.Date
import javax.inject.Inject
@ -561,12 +563,18 @@ private fun openTaskForm(task: Task) {
val bundle = Bundle()
bundle.putString(TaskFormActivity.TASK_TYPE_KEY, task.type?.value)
bundle.putString(TaskFormActivity.TASK_ID_KEY, task.id)
bundle.putString(TaskFormActivity.GROUP_ID_KEY, task.group?.groupID)
bundle.putDouble(TaskFormActivity.TASK_VALUE_KEY, task.value)
if (viewModel.canEditTask(task)) {
MainNavigationController.navigate(R.id.taskFormActivity, bundle)
} else {
MainNavigationController.navigate(R.id.taskSummaryActivity, bundle)
lifecycleScope.launchCatching {
val id = if (viewModel.canEditTask(task)) {
R.id.taskFormActivity
} else {
R.id.taskSummaryActivity
}
withContext(Dispatchers.Main) {
MainNavigationController.navigate(id, bundle)
}
}
TasksFragment.lastTaskFormOpen = Date()
}

View file

@ -116,7 +116,9 @@ class TasksFragment : BaseMainFragment<FragmentViewpagerBinding>(), SearchView.O
}
binding?.viewPager?.currentItem = binding?.viewPager?.currentItem ?: 0
bottomNavigation?.listener = this
bottomNavigation?.canAddTasks = viewModel.isPersonalBoard
lifecycleScope.launchCatching {
bottomNavigation?.canAddTasks = viewModel.canAddTasks()
}
activity?.binding?.content?.toolbarTitle?.setOnClickListener {
viewModel.cycleOwnerIDs()
@ -324,6 +326,9 @@ class TasksFragment : BaseMainFragment<FragmentViewpagerBinding>(), SearchView.O
val bundle = Bundle()
bundle.putString(TaskFormActivity.TASK_TYPE_KEY, type.value)
if (!viewModel.isPersonalBoard) {
bundle.putString(TaskFormActivity.GROUP_ID_KEY, viewModel.ownerID.value)
}
bundle.putStringArrayList(TaskFormActivity.SELECTED_TAGS_KEY, ArrayList(viewModel.tags))
val intent = Intent(activity, TaskFormActivity::class.java)
@ -417,7 +422,8 @@ class TasksFragment : BaseMainFragment<FragmentViewpagerBinding>(), SearchView.O
activity?.title = viewModel.ownerTitle
}
viewModel.userViewModel.currentTeamPlan.value = viewModel.teamPlans[viewModel.ownerID.value]
val isPersonalBoard = viewModel.isPersonalBoard
bottomNavigation?.canAddTasks = isPersonalBoard
lifecycleScope.launchCatching {
bottomNavigation?.canAddTasks = viewModel.canAddTasks()
}
}
}

View file

@ -24,6 +24,7 @@ import io.realm.Case
import io.realm.OrderedRealmCollection
import io.realm.RealmQuery
import io.realm.Sort
import kotlinx.coroutines.flow.firstOrNull
import kotlinx.coroutines.launch
import java.util.Date
import javax.inject.Inject
@ -310,11 +311,22 @@ class TasksViewModel : BaseViewModel(), GroupPlanInfoProvider {
return task.isAssignedToUser(userViewModel.userID) || task.group?.assignedUsers?.isEmpty() != false
}
override fun canEditTask(task: Task): Boolean {
override suspend fun canEditTask(task: Task): Boolean {
if (!task.isGroupTask) {
return true
}
return false
val groupID = task.group?.groupID ?: return true
val group = userRepository.getTeamPlan(groupID).firstOrNull()
return group?.hasTaskEditPrivileges(userViewModel.userID) ?: false
}
override suspend fun canAddTasks(): Boolean {
if (isPersonalBoard) {
return true
}
val groupID = ownerID.value ?: return true
val group = userRepository.getTeamPlan(groupID).firstOrNull()
return group?.hasTaskEditPrivileges(userViewModel.userID) ?: false
}
override fun assignedTextForTask(resources: Resources, assignedUsers: List<String>): String {

View file

@ -1,8 +1,6 @@
package com.habitrpg.android.habitica.ui.views
import android.graphics.Bitmap
import androidx.compose.animation.AnimatedVisibility
import androidx.compose.animation.core.animateFloatAsState
import androidx.compose.animation.fadeIn
import androidx.compose.animation.fadeOut
import androidx.compose.animation.slideInHorizontally
@ -25,8 +23,6 @@ import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.width
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.LinearProgressIndicator
import androidx.compose.material.ProgressIndicatorDefaults
import androidx.compose.material.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.collectAsState
@ -35,7 +31,6 @@ import androidx.compose.runtime.livedata.observeAsState
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.ColorFilter
import androidx.compose.ui.graphics.asImageBitmap
import androidx.compose.ui.platform.LocalContext
@ -51,10 +46,6 @@ import com.habitrpg.android.habitica.R
import com.habitrpg.android.habitica.helpers.MainNavigationController
import com.habitrpg.android.habitica.models.user.User
import com.habitrpg.android.habitica.ui.viewmodels.MainUserViewModel
import com.habitrpg.common.habitica.helpers.NumberAbbreviator
import java.lang.Double.max
import java.math.RoundingMode
import java.text.NumberFormat
@Composable
fun UserLevelText(user: User) {
@ -77,36 +68,6 @@ fun UserLevelText(user: User) {
)
}
@Composable
fun CurrencyText(
currency: String,
value: Double,
modifier: Modifier = Modifier,
decimals: Int = 2,
minForAbbrevation: Int = 0
) {
Row(verticalAlignment = Alignment.CenterVertically) {
when (currency) {
"gold" -> HabiticaIconsHelper.imageOfGold()
"gems" -> HabiticaIconsHelper.imageOfGem()
"hourglasses" -> HabiticaIconsHelper.imageOfHourglass()
else -> null
}?.asImageBitmap()?.let { Image(it, null, Modifier.padding(end = 5.dp)) }
Text(
NumberAbbreviator.abbreviate(null, value, decimals, minForAbbrevation),
color = when (currency) {
"gold" -> colorResource(R.color.text_gold)
"gems" -> colorResource(R.color.text_green)
"hourglasses" -> colorResource(R.color.text_brand)
else -> colorResource(R.color.text_primary)
},
fontSize = 12.sp,
fontWeight = FontWeight.SemiBold,
modifier = modifier
)
}
}
@Composable
fun AppHeaderView(
viewModel: MainUserViewModel,
@ -245,60 +206,4 @@ fun AppHeaderView(
CurrencyText("gems", user?.gemCount?.toDouble() ?: 0.0)
}
}
}
@Composable
fun LabeledBar(
icon: Bitmap,
label: String,
color: Color,
value: Double,
maxValue: Double,
displayCompact: Boolean,
modifier: Modifier = Modifier
) {
val formatter = NumberFormat.getInstance()
formatter.maximumFractionDigits = 1
formatter.roundingMode = RoundingMode.UP
formatter.isGroupingUsed = true
val cleanedMaxVlaue = max(1.0, maxValue)
val animatedValue = animateFloatAsState(
targetValue = value.toFloat(),
animationSpec = ProgressIndicatorDefaults.ProgressAnimationSpec,
).value
Row(verticalAlignment = Alignment.CenterVertically, modifier = modifier) {
AnimatedVisibility(
visible = !displayCompact,
enter = slideInHorizontally { -18 },
exit = slideOutHorizontally { -18 }) {
Image(icon.asImageBitmap(), null, modifier = Modifier.padding(end = 8.dp))
}
Column(modifier = Modifier.weight(1f)) {
LinearProgressIndicator(
progress = (animatedValue / cleanedMaxVlaue).toFloat(),
Modifier
.fillMaxWidth()
.clip(CircleShape)
.height(8.dp),
backgroundColor = colorResource(R.color.window_background),
color = color
)
AnimatedVisibility(visible = !displayCompact) {
Row(
verticalAlignment = Alignment.CenterVertically,
modifier = Modifier.padding(top = 2.dp)
) {
Text(
"${formatter.format(animatedValue)} / ${formatter.format(cleanedMaxVlaue)}",
fontSize = 12.sp,
color = colorResource(R.color.text_ternary)
)
Spacer(Modifier.weight(1f))
Text(label, fontSize = 12.sp, color = colorResource(R.color.text_ternary))
}
}
}
}
}

View file

@ -0,0 +1,17 @@
package com.habitrpg.android.habitica.ui.views
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.ui.geometry.Size
import androidx.compose.ui.graphics.Outline
import androidx.compose.ui.graphics.Shape
import androidx.compose.ui.unit.Density
import androidx.compose.ui.unit.LayoutDirection
object AvatarCircleShape : Shape {
override fun createOutline(
size: Size,
layoutDirection: LayoutDirection,
density: Density
): Outline =
CircleShape.createOutline(size, 20f, 20f, 20f, 20f, layoutDirection)
}

View file

@ -0,0 +1,134 @@
package com.habitrpg.android.habitica.ui.views
import android.app.Activity
import android.util.Log
import android.view.ViewGroup
import androidx.activity.compose.BackHandler
import androidx.compose.foundation.background
import androidx.compose.foundation.border
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.ExperimentalMaterialApi
import androidx.compose.material.MaterialTheme
import androidx.compose.material.ModalBottomSheetLayout
import androidx.compose.material.ModalBottomSheetValue
import androidx.compose.material.rememberModalBottomSheetState
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.platform.ComposeView
import androidx.compose.ui.res.colorResource
import androidx.compose.ui.unit.dp
import androidx.fragment.app.Fragment
import com.habitrpg.android.habitica.R
import com.habitrpg.android.habitica.ui.theme.HabiticaTheme
import kotlinx.coroutines.launch
// Extension for Activity
fun Activity.showAsBottomSheet(content: @Composable (() -> Unit) -> Unit) {
val viewGroup = this.findViewById(android.R.id.content) as ViewGroup
addContentToView(viewGroup, content)
}
// Extension for Fragment
fun Fragment.showAsBottomSheet(content: @Composable (() -> Unit) -> Unit) {
val viewGroup = requireActivity().findViewById(android.R.id.content) as ViewGroup
addContentToView(viewGroup, content)
}
// Helper method
private fun addContentToView(
viewGroup: ViewGroup,
content: @Composable (() -> Unit) -> Unit
) {
viewGroup.addView(
ComposeView(viewGroup.context).apply {
setContent {
HabiticaTheme {
BottomSheetWrapper(viewGroup, this, content)
}
}
}
)
}
@OptIn(ExperimentalMaterialApi::class)
@Composable
private fun BottomSheetWrapper(
parent: ViewGroup,
composeView: ComposeView,
content: @Composable (() -> Unit) -> Unit
) {
val TAG = parent::class.java.simpleName
val coroutineScope = rememberCoroutineScope()
val modalBottomSheetState = rememberModalBottomSheetState(ModalBottomSheetValue.Hidden)
var isSheetOpened by remember { mutableStateOf(false) }
ModalBottomSheetLayout(
sheetBackgroundColor = Color.Transparent,
sheetState = modalBottomSheetState,
sheetContent = {
Column(
horizontalAlignment = Alignment.CenterHorizontally,
modifier = Modifier
.border(
2.dp,
colorResource(R.color.window_background),
RoundedCornerShape(topStart = 12.dp, topEnd = 12.dp)
)
.background(
MaterialTheme.colors.background,
RoundedCornerShape(topStart = 12.dp, topEnd = 12.dp)
)
.padding(vertical = 8.dp)
) {
Box(
modifier = Modifier
.padding(bottom = 16.dp)
.background(colorResource(R.color.content_background_offset))
.size(24.dp, 3.dp)
)
content {
// Action passed for clicking close button in the content
coroutineScope.launch {
modalBottomSheetState.hide() // will trigger the LaunchedEffect
}
}
}
}
) {}
BackHandler {
coroutineScope.launch {
modalBottomSheetState.hide() // will trigger the LaunchedEffect
}
}
// Take action based on hidden state
LaunchedEffect(modalBottomSheetState.currentValue) {
when (modalBottomSheetState.currentValue) {
ModalBottomSheetValue.Hidden -> {
when {
isSheetOpened -> parent.removeView(composeView)
else -> {
isSheetOpened = true
modalBottomSheetState.show()
}
}
}
else -> {
Log.i(TAG, "Bottom sheet ${modalBottomSheetState.currentValue} state")
}
}
}
}

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