Compare commits
62 Commits
2abc9808e2
...
2.1.1+35
Author | SHA1 | Date | |
---|---|---|---|
b50191970e | |||
1b69e6dd42 | |||
39fb4d474f | |||
392aebcad7 | |||
e9e3a4c474 | |||
7182336a0d | |||
be98fe133d | |||
e458943f56 | |||
eb125fc436 | |||
dc78f39969 | |||
f5c06bc89c | |||
d6d60e60a9 | |||
435b730f3b | |||
73468c5c6d | |||
8db6513eef | |||
65a8f1e6c3 | |||
2671ffad4b | |||
8a628823e0 | |||
94d19a1524 | |||
d98f6c8d18 | |||
6d0f62016a | |||
7e0faba5db | |||
7508a54907 | |||
2eb1f4b52b | |||
00678c0ac8 | |||
abc21f858b | |||
d67e33a41d | |||
4daff41b3e | |||
f92418ea4b | |||
89c912a35b | |||
09ad917e5d | |||
5c377dc0b6 | |||
8bdaf05223 | |||
e920bd954c | |||
e395ac87c5 | |||
026a4dfb27 | |||
df18370bde | |||
80a66136ce | |||
1f8d47f6c3 | |||
b750cc3c67 | |||
b618fcc6da | |||
f763c7515a | |||
c7d5cb48ac | |||
39470d7dbf | |||
4328de21ef | |||
a3a0e8c7a2 | |||
210c73a831 | |||
edaeae386e | |||
be66ea354e | |||
d7c1ffe3cc | |||
240ad7dc7e | |||
bb5fe9c380 | |||
1347aacbc5 | |||
8880647360 | |||
717bccbf3f | |||
018441ea0b | |||
336bb88ca4 | |||
811fc40d79 | |||
e05209ba3c | |||
623095473e | |||
f47f1b175a | |||
3b1d291037 |
16
README.md
16
README.md
@ -1,16 +0,0 @@
|
||||
# surface
|
||||
|
||||
A new Flutter project.
|
||||
|
||||
## Getting Started
|
||||
|
||||
This project is a starting point for a Flutter application.
|
||||
|
||||
A few resources to get you started if this is your first Flutter project:
|
||||
|
||||
- [Lab: Write your first Flutter app](https://docs.flutter.dev/get-started/codelab)
|
||||
- [Cookbook: Useful Flutter samples](https://docs.flutter.dev/cookbook)
|
||||
|
||||
For help getting started with Flutter development, view the
|
||||
[online documentation](https://docs.flutter.dev/), which offers tutorials,
|
||||
samples, guidance on mobile development, and a full API reference.
|
@ -9,14 +9,32 @@ plugins {
|
||||
id "dev.flutter.flutter-gradle-plugin"
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation "androidx.glance:glance:1.1.1"
|
||||
implementation "androidx.glance:glance-appwidget:1.1.1"
|
||||
implementation 'androidx.compose.foundation:foundation-layout-android:1.7.6'
|
||||
implementation 'com.google.code.gson:gson:2.10.1'
|
||||
implementation 'com.squareup.okhttp3:okhttp:4.12.0'
|
||||
implementation 'io.coil-kt.coil3:coil-compose:3.0.4'
|
||||
implementation 'io.coil-kt.coil3:coil-network-okhttp:3.0.4'
|
||||
}
|
||||
|
||||
android {
|
||||
buildFeatures {
|
||||
compose true
|
||||
}
|
||||
|
||||
namespace = "dev.solsynth.solian"
|
||||
compileSdk = flutter.compileSdkVersion
|
||||
ndkVersion = "27.0.12077973"
|
||||
|
||||
compileOptions {
|
||||
sourceCompatibility JavaVersion.VERSION_17
|
||||
targetCompatibility JavaVersion.VERSION_17
|
||||
sourceCompatibility JavaVersion.VERSION_17
|
||||
targetCompatibility JavaVersion.VERSION_17
|
||||
}
|
||||
|
||||
composeOptions {
|
||||
kotlinCompilerExtensionVersion = "1.4.8"
|
||||
}
|
||||
|
||||
kotlinOptions {
|
||||
@ -24,21 +42,25 @@ android {
|
||||
}
|
||||
|
||||
defaultConfig {
|
||||
// TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html).
|
||||
applicationId = "dev.solsynth.solian"
|
||||
// You can update the following values to match your application needs.
|
||||
// For more information, see: https://flutter.dev/to/review-gradle-config.
|
||||
minSdk = flutter.minSdkVersion
|
||||
minSdk = 26
|
||||
targetSdk = flutter.targetSdkVersion
|
||||
versionCode = flutter.versionCode
|
||||
versionName = flutter.versionName
|
||||
}
|
||||
|
||||
buildTypes {
|
||||
debug {
|
||||
debuggable true
|
||||
|
||||
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
|
||||
}
|
||||
release {
|
||||
// TODO: Add your own signing config for the release build.
|
||||
// Signing with the debug keys for now, so `flutter run --release` works.
|
||||
signingConfig = signingConfigs.debug
|
||||
|
||||
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -11,6 +11,7 @@
|
||||
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN" android:maxSdkVersion="30" />
|
||||
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" android:maxSdkVersion="29" />
|
||||
<uses-permission android:name="android.permission.POST_NOTIFICATIONS"/>
|
||||
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
|
||||
|
||||
<application
|
||||
android:label="Solian"
|
||||
@ -20,12 +21,44 @@
|
||||
<activity
|
||||
android:name=".MainActivity"
|
||||
android:exported="true"
|
||||
android:launchMode="singleTop"
|
||||
android:launchMode="singleTask"
|
||||
android:taskAffinity=""
|
||||
android:theme="@style/LaunchTheme"
|
||||
android:configChanges="orientation|keyboardHidden|keyboard|screenSize|smallestScreenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode"
|
||||
android:hardwareAccelerated="true"
|
||||
android:windowSoftInputMode="adjustResize">
|
||||
<!-- Widgets Indents -->
|
||||
<intent-filter>
|
||||
<action android:name="es.antonborri.home_widget.action.LAUNCH" />
|
||||
</intent-filter>
|
||||
|
||||
<!-- Sharing Intents -->
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.SEND" />
|
||||
<category android:name="android.intent.category.DEFAULT" />
|
||||
<data android:mimeType="text/*" />
|
||||
</intent-filter>
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.SEND" />
|
||||
<category android:name="android.intent.category.DEFAULT" />
|
||||
<data android:mimeType="image/*" />
|
||||
</intent-filter>
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.SEND_MULTIPLE" />
|
||||
<category android:name="android.intent.category.DEFAULT" />
|
||||
<data android:mimeType="image/*" />
|
||||
</intent-filter>
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.SEND" />
|
||||
<category android:name="android.intent.category.DEFAULT" />
|
||||
<data android:mimeType="video/*" />
|
||||
</intent-filter>
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.SEND_MULTIPLE" />
|
||||
<category android:name="android.intent.category.DEFAULT" />
|
||||
<data android:mimeType="video/*" />
|
||||
</intent-filter>
|
||||
|
||||
<!-- Specifies an Android theme to apply to this Activity as soon as
|
||||
the Android process has started. This theme is visible to the user
|
||||
while the Flutter UI initializes. After that, this theme continues
|
||||
@ -44,7 +77,30 @@
|
||||
<meta-data
|
||||
android:name="flutterEmbedding"
|
||||
android:value="2" />
|
||||
|
||||
<!-- Widgets -->
|
||||
<receiver android:name=".widgets.CheckInWidgetReceiver"
|
||||
android:label="Check In"
|
||||
android:exported="true">
|
||||
<intent-filter>
|
||||
<action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
|
||||
</intent-filter>
|
||||
<meta-data
|
||||
android:name="android.appwidget.provider"
|
||||
android:resource="@xml/check_in_widget" />
|
||||
</receiver>
|
||||
<receiver android:name=".widgets.RandomPostWidgetReceiver"
|
||||
android:label="Random Post"
|
||||
android:exported="true">
|
||||
<intent-filter>
|
||||
<action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
|
||||
</intent-filter>
|
||||
<meta-data
|
||||
android:name="android.appwidget.provider"
|
||||
android:resource="@xml/random_post_widget" />
|
||||
</receiver>
|
||||
</application>
|
||||
|
||||
<!-- Required to query activities that can process text, see:
|
||||
https://developer.android.com/training/package-visibility and
|
||||
https://developer.android.com/reference/android/content/Intent#ACTION_PROCESS_TEXT.
|
||||
|
@ -0,0 +1,6 @@
|
||||
package dev.solsynth.solian.data
|
||||
|
||||
import androidx.annotation.Keep
|
||||
|
||||
@Keep
|
||||
data class SolarPagination<T>(val count: Int, val data: List<T>)
|
@ -0,0 +1,35 @@
|
||||
package dev.solsynth.solian.data
|
||||
|
||||
import androidx.annotation.Keep
|
||||
import java.time.Instant
|
||||
|
||||
@Keep
|
||||
data class SolarPost(
|
||||
val id: Int,
|
||||
val body: SolarPostBody,
|
||||
val publisher: SolarPublisher,
|
||||
val publisherId: Int,
|
||||
val createdAt: Instant,
|
||||
val updatedAt: Instant,
|
||||
val editedAt: Instant?,
|
||||
val publishedAt: Instant?
|
||||
)
|
||||
|
||||
@Keep
|
||||
data class SolarPostBody(
|
||||
val content: String?,
|
||||
val title: String?,
|
||||
val description: String?,
|
||||
)
|
||||
|
||||
@Keep
|
||||
data class SolarPublisher(
|
||||
val id: Int,
|
||||
val name: String,
|
||||
val nick: String,
|
||||
val description: String?,
|
||||
val avatar: String?,
|
||||
val banner: String?,
|
||||
val createdAt: Instant,
|
||||
val updatedAt: Instant
|
||||
)
|
@ -0,0 +1,38 @@
|
||||
package dev.solsynth.solian.data
|
||||
|
||||
import androidx.annotation.Keep
|
||||
import com.google.gson.JsonDeserializationContext
|
||||
import com.google.gson.JsonDeserializer
|
||||
import com.google.gson.JsonElement
|
||||
import com.google.gson.JsonParseException
|
||||
import com.google.gson.JsonPrimitive
|
||||
import com.google.gson.JsonSerializationContext
|
||||
import com.google.gson.JsonSerializer
|
||||
import java.lang.reflect.Type
|
||||
import java.time.Instant
|
||||
import java.time.format.DateTimeFormatter
|
||||
|
||||
@Keep
|
||||
class InstantAdapter : JsonSerializer<Instant?>,
|
||||
JsonDeserializer<Instant?> {
|
||||
override fun serialize(
|
||||
src: Instant?,
|
||||
typeOfSrc: Type?,
|
||||
context: JsonSerializationContext?
|
||||
): JsonElement {
|
||||
return JsonPrimitive(formatter.format(src))
|
||||
}
|
||||
|
||||
@Throws(JsonParseException::class)
|
||||
override fun deserialize(
|
||||
json: JsonElement,
|
||||
typeOfT: Type?,
|
||||
context: JsonDeserializationContext?
|
||||
): Instant {
|
||||
return Instant.parse(json.asString)
|
||||
}
|
||||
|
||||
companion object {
|
||||
private val formatter: DateTimeFormatter = DateTimeFormatter.ISO_INSTANT
|
||||
}
|
||||
}
|
@ -0,0 +1,19 @@
|
||||
package dev.solsynth.solian.data
|
||||
|
||||
import androidx.annotation.Keep
|
||||
import java.time.Instant
|
||||
|
||||
@Keep
|
||||
data class SolarUser(
|
||||
val id: Int,
|
||||
val name: String,
|
||||
val nick: String
|
||||
)
|
||||
|
||||
@Keep
|
||||
data class SolarCheckInRecord(
|
||||
val id: Int,
|
||||
val resultTier: Int,
|
||||
val resultExperience: Int,
|
||||
val createdAt: Instant
|
||||
)
|
@ -0,0 +1,119 @@
|
||||
import android.content.Context
|
||||
import android.net.Uri
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.unit.sp
|
||||
import androidx.glance.GlanceId
|
||||
import androidx.glance.GlanceModifier
|
||||
import androidx.glance.GlanceTheme
|
||||
import androidx.glance.action.clickable
|
||||
import androidx.glance.appwidget.GlanceAppWidget
|
||||
import androidx.glance.appwidget.provideContent
|
||||
import androidx.glance.background
|
||||
import androidx.glance.currentState
|
||||
import androidx.glance.layout.Alignment
|
||||
import androidx.glance.layout.Column
|
||||
import androidx.glance.layout.Row
|
||||
import androidx.glance.layout.Spacer
|
||||
import androidx.glance.layout.fillMaxHeight
|
||||
import androidx.glance.layout.fillMaxWidth
|
||||
import androidx.glance.layout.height
|
||||
import androidx.glance.layout.padding
|
||||
import androidx.glance.state.GlanceStateDefinition
|
||||
import androidx.glance.text.FontFamily
|
||||
import androidx.glance.text.Text
|
||||
import androidx.glance.text.TextStyle
|
||||
import com.google.gson.FieldNamingPolicy
|
||||
import com.google.gson.GsonBuilder
|
||||
import dev.solsynth.solian.MainActivity
|
||||
import dev.solsynth.solian.data.InstantAdapter
|
||||
import dev.solsynth.solian.data.SolarCheckInRecord
|
||||
import es.antonborri.home_widget.actionStartActivity
|
||||
import java.time.Instant
|
||||
import java.time.LocalDate
|
||||
import java.time.OffsetDateTime
|
||||
import java.time.ZoneId
|
||||
import java.time.format.DateTimeFormatter
|
||||
|
||||
class CheckInWidget : GlanceAppWidget() {
|
||||
override val stateDefinition: GlanceStateDefinition<*>?
|
||||
get() = HomeWidgetGlanceStateDefinition()
|
||||
|
||||
override suspend fun provideGlance(context: Context, id: GlanceId) {
|
||||
provideContent {
|
||||
GlanceTheme {
|
||||
GlanceContent(context, currentState())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun GlanceContent(context: Context, currentState: HomeWidgetGlanceState) {
|
||||
val gson =
|
||||
GsonBuilder()
|
||||
.setFieldNamingPolicy(FieldNamingPolicy.LOWER_CASE_WITH_UNDERSCORES)
|
||||
.registerTypeAdapter(Instant::class.java, InstantAdapter())
|
||||
.create()
|
||||
val resultTierSymbols = listOf("大凶", "凶", "中平", "吉", "大吉")
|
||||
|
||||
val prefs = currentState.preferences
|
||||
val checkInRaw: String? = prefs.getString("pas_check_in_record", null)
|
||||
|
||||
val checkIn: SolarCheckInRecord? =
|
||||
checkInRaw?.let { checkInRaw ->
|
||||
gson.fromJson(checkInRaw, SolarCheckInRecord::class.java)
|
||||
} ?: null;
|
||||
|
||||
Column(
|
||||
modifier = GlanceModifier
|
||||
.fillMaxWidth()
|
||||
.fillMaxHeight()
|
||||
.background(GlanceTheme.colors.widgetBackground)
|
||||
.padding(16.dp)
|
||||
.clickable(
|
||||
onClick = actionStartActivity<MainActivity>(
|
||||
context,
|
||||
Uri.parse("https://sn.solsynth.dev")
|
||||
)
|
||||
)
|
||||
) {
|
||||
if (checkIn != null) {
|
||||
val dateFormatter = DateTimeFormatter.ofPattern("EEE, MM/dd")
|
||||
|
||||
val checkDate = checkIn.createdAt.atZone(ZoneId.of("UTC")).toLocalDate()
|
||||
val currentDate = LocalDate.now()
|
||||
if (checkDate.isEqual(currentDate)) {
|
||||
Column {
|
||||
Text(
|
||||
text = resultTierSymbols[checkIn.resultTier],
|
||||
style = TextStyle(fontSize = 17.sp)
|
||||
)
|
||||
Text(
|
||||
text = "+${checkIn.resultExperience} EXP",
|
||||
style = TextStyle(fontSize = 13.sp, fontFamily = FontFamily.Monospace)
|
||||
)
|
||||
}
|
||||
Spacer(modifier = GlanceModifier.height(8.dp))
|
||||
Row(horizontalAlignment = Alignment.CenterHorizontally) {
|
||||
Text(
|
||||
text = OffsetDateTime.ofInstant(
|
||||
checkIn.createdAt,
|
||||
ZoneId.systemDefault()
|
||||
)
|
||||
.format(dateFormatter),
|
||||
style = TextStyle(fontSize = 11.sp)
|
||||
)
|
||||
}
|
||||
|
||||
return@Column;
|
||||
}
|
||||
}
|
||||
|
||||
Text(
|
||||
text = "You haven't checked in today",
|
||||
style = TextStyle(fontSize = 15.sp)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,8 @@
|
||||
package dev.solsynth.solian.widgets
|
||||
|
||||
import CheckInWidget
|
||||
import HomeWidgetGlanceWidgetReceiver
|
||||
|
||||
class CheckInWidgetReceiver : HomeWidgetGlanceWidgetReceiver<CheckInWidget>() {
|
||||
override val glanceAppWidget = CheckInWidget()
|
||||
}
|
@ -0,0 +1,156 @@
|
||||
import HomeWidgetGlanceState
|
||||
import HomeWidgetGlanceStateDefinition
|
||||
import android.content.Context
|
||||
import android.net.Uri
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.unit.sp
|
||||
import androidx.glance.GlanceId
|
||||
import androidx.glance.GlanceModifier
|
||||
import androidx.glance.GlanceTheme
|
||||
import androidx.glance.action.clickable
|
||||
import androidx.glance.appwidget.GlanceAppWidget
|
||||
import androidx.glance.appwidget.provideContent
|
||||
import androidx.glance.background
|
||||
import androidx.glance.currentState
|
||||
import androidx.glance.layout.Alignment
|
||||
import androidx.glance.layout.Column
|
||||
import androidx.glance.layout.Row
|
||||
import androidx.glance.layout.Spacer
|
||||
import androidx.glance.layout.fillMaxHeight
|
||||
import androidx.glance.layout.fillMaxSize
|
||||
import androidx.glance.layout.fillMaxWidth
|
||||
import androidx.glance.layout.height
|
||||
import androidx.glance.layout.padding
|
||||
import androidx.glance.layout.width
|
||||
import androidx.glance.state.GlanceStateDefinition
|
||||
import androidx.glance.text.FontFamily
|
||||
import androidx.glance.text.FontWeight
|
||||
import androidx.glance.text.Text
|
||||
import androidx.glance.text.TextStyle
|
||||
import com.google.gson.FieldNamingPolicy
|
||||
import com.google.gson.GsonBuilder
|
||||
import dev.solsynth.solian.MainActivity
|
||||
import dev.solsynth.solian.data.InstantAdapter
|
||||
import dev.solsynth.solian.data.SolarPost
|
||||
import es.antonborri.home_widget.actionStartActivity
|
||||
import java.time.Instant
|
||||
import java.time.LocalDateTime
|
||||
import java.time.ZoneId
|
||||
import java.time.format.DateTimeFormatter
|
||||
|
||||
class RandomPostWidget : GlanceAppWidget() {
|
||||
override val stateDefinition: GlanceStateDefinition<*>?
|
||||
get() = HomeWidgetGlanceStateDefinition()
|
||||
|
||||
override suspend fun provideGlance(context: Context, id: GlanceId) {
|
||||
provideContent {
|
||||
GlanceTheme {
|
||||
GlanceContent(context, currentState())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun GlanceContent(
|
||||
context: Context,
|
||||
currentState: HomeWidgetGlanceState,
|
||||
) {
|
||||
val prefs = currentState.preferences
|
||||
val postRaw = prefs.getString("int_random_post", null)
|
||||
|
||||
val gson =
|
||||
GsonBuilder()
|
||||
.setFieldNamingPolicy(FieldNamingPolicy.LOWER_CASE_WITH_UNDERSCORES)
|
||||
.registerTypeAdapter(Instant::class.java, InstantAdapter())
|
||||
.create()
|
||||
|
||||
val data: SolarPost? = postRaw?.let { postRaw ->
|
||||
gson.fromJson(postRaw, SolarPost::class.java)
|
||||
} ?: null;
|
||||
|
||||
Column(
|
||||
modifier = GlanceModifier
|
||||
.fillMaxWidth()
|
||||
.fillMaxHeight()
|
||||
.background(GlanceTheme.colors.widgetBackground)
|
||||
.padding(16.dp)
|
||||
.clickable(
|
||||
onClick = actionStartActivity<MainActivity>(
|
||||
context,
|
||||
Uri.parse("https://sn.solsynth.dev/posts/${data!!.id}")
|
||||
)
|
||||
)
|
||||
) {
|
||||
if (data != null) {
|
||||
Row(verticalAlignment = Alignment.CenterVertically) {
|
||||
Text(
|
||||
text = data.publisher.nick,
|
||||
style = TextStyle(fontSize = 15.sp)
|
||||
)
|
||||
Spacer(modifier = GlanceModifier.width(8.dp))
|
||||
Text(
|
||||
text = "@${data.publisher.name}",
|
||||
style = TextStyle(fontSize = 13.sp, fontFamily = FontFamily.Monospace)
|
||||
)
|
||||
}
|
||||
|
||||
Spacer(modifier = GlanceModifier.height(8.dp))
|
||||
|
||||
if (data.body.title != null) {
|
||||
Text(
|
||||
text = data.body.title,
|
||||
style = TextStyle(fontSize = 25.sp)
|
||||
)
|
||||
}
|
||||
if (data.body.description != null) {
|
||||
Text(
|
||||
text = data.body.description,
|
||||
style = TextStyle(fontSize = 19.sp)
|
||||
)
|
||||
}
|
||||
|
||||
if (data.body.title != null || data.body.description != null) {
|
||||
Spacer(modifier = GlanceModifier.height(8.dp))
|
||||
}
|
||||
|
||||
Text(
|
||||
text = data.body.content ?: "No content",
|
||||
style = TextStyle(fontSize = 15.sp),
|
||||
)
|
||||
|
||||
Spacer(modifier = GlanceModifier.height(8.dp))
|
||||
|
||||
|
||||
Text(
|
||||
LocalDateTime.ofInstant(data.createdAt, ZoneId.systemDefault())
|
||||
.format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm")),
|
||||
style = TextStyle(fontSize = 13.sp),
|
||||
)
|
||||
|
||||
Text(
|
||||
"#${data.id}",
|
||||
style = TextStyle(fontSize = 11.sp, fontWeight = FontWeight.Bold),
|
||||
)
|
||||
|
||||
return@Column;
|
||||
}
|
||||
|
||||
Column(
|
||||
modifier = GlanceModifier.fillMaxSize(),
|
||||
verticalAlignment = Alignment.Vertical.CenterVertically,
|
||||
horizontalAlignment = Alignment.Horizontal.CenterHorizontally
|
||||
) {
|
||||
Text(
|
||||
text = "Unable to fetch post",
|
||||
style = TextStyle(fontSize = 17.sp, fontWeight = FontWeight.Bold)
|
||||
)
|
||||
Text(
|
||||
text = "Check your internet connection",
|
||||
style = TextStyle(fontSize = 15.sp)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,8 @@
|
||||
package dev.solsynth.solian.widgets
|
||||
|
||||
import RandomPostWidget
|
||||
import HomeWidgetGlanceWidgetReceiver
|
||||
|
||||
class RandomPostWidgetReceiver : HomeWidgetGlanceWidgetReceiver<RandomPostWidget>() {
|
||||
override val glanceAppWidget = RandomPostWidget()
|
||||
}
|
7
android/app/src/main/res/xml/check_in_widget.xml
Normal file
7
android/app/src/main/res/xml/check_in_widget.xml
Normal file
@ -0,0 +1,7 @@
|
||||
<appwidget-provider xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:initialLayout="@layout/glance_default_loading_layout"
|
||||
android:minWidth="40dp"
|
||||
android:minHeight="40dp"
|
||||
android:resizeMode="horizontal|vertical"
|
||||
android:updatePeriodMillis="10000">
|
||||
</appwidget-provider>
|
7
android/app/src/main/res/xml/random_post_widget.xml
Normal file
7
android/app/src/main/res/xml/random_post_widget.xml
Normal file
@ -0,0 +1,7 @@
|
||||
<appwidget-provider xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:initialLayout="@layout/glance_default_loading_layout"
|
||||
android:minWidth="240dp"
|
||||
android:minHeight="40dp"
|
||||
android:resizeMode="horizontal|vertical"
|
||||
android:updatePeriodMillis="10000">
|
||||
</appwidget-provider>
|
14
android/app/src/proguard-rules.pro
vendored
Normal file
14
android/app/src/proguard-rules.pro
vendored
Normal file
@ -0,0 +1,14 @@
|
||||
-keepclassmembers class kotlin.Metadata { *; }
|
||||
-keep class dev.solsynth.solian.** { *; }
|
||||
-keep public class dev.solsynth.solian.data.** { public *; }
|
||||
-keepclassmembers class dev.solsynth.solian.data.** { *; }
|
||||
|
||||
-keepattributes *Annotation*
|
||||
-keepattributes Signature
|
||||
-keepattributes EnclosingMethod
|
||||
|
||||
-keep class com.google.gson.** { *; }
|
||||
|
||||
-keepclassmembers class * {
|
||||
@com.google.gson.annotations.SerializedName <fields>;
|
||||
}
|
@ -3,6 +3,15 @@ allprojects {
|
||||
google()
|
||||
mavenCentral()
|
||||
}
|
||||
configurations.all {
|
||||
resolutionStrategy {
|
||||
eachDependency {
|
||||
if ((requested.group == "androidx.work") && (requested.name.startsWith("work-runtime"))) {
|
||||
useVersion("2.9.1")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
rootProject.buildDir = "../build"
|
||||
|
@ -18,7 +18,7 @@ pluginManagement {
|
||||
|
||||
plugins {
|
||||
id "dev.flutter.flutter-plugin-loader" version "1.0.0"
|
||||
id "com.android.application" version '8.7.2' apply false
|
||||
id "com.android.application" version '8.7.3' apply false
|
||||
// START: FlutterFire Configuration
|
||||
id "com.google.gms.google-services" version "4.3.15" apply false
|
||||
id "com.google.firebase.crashlytics" version "2.8.1" apply false
|
||||
|
@ -128,6 +128,7 @@
|
||||
"one": "{} social point",
|
||||
"other": "{} social points"
|
||||
},
|
||||
"publisherAffiliatedBy": "Affiliated by {}",
|
||||
"publisherRunBy": "Run by {}",
|
||||
"fieldPublisherBelongToRealm": "Belongs to",
|
||||
"fieldPublisherBelongToRealmUnset": "Unset Publisher Belongs to Realm",
|
||||
@ -189,6 +190,13 @@
|
||||
"settingsNetworkServerPreset": "Present HyperNet Server",
|
||||
"settingsNetworkServerPresetDescription": "You can choose one of our preset HyperNet server addresses from the list on the right.",
|
||||
"settingsNetworkServerSaved": "Server address saved.",
|
||||
"settingsPerformance": "Performance",
|
||||
"settingsImageQuality": "Image Quality",
|
||||
"settingsImageQualityDescription": "Set the image quality, it will affect the decoding speed of the image.",
|
||||
"settingsImageQualityLowest": "Lowest",
|
||||
"settingsImageQualityLow": "Low",
|
||||
"settingsImageQualityMedium": "Medium",
|
||||
"settingsImageQualityHigh": "High",
|
||||
"settingsMisc": "Misc",
|
||||
"settingsMiscAbout": "About",
|
||||
"settingsMiscAboutDescription": "View the version information of Solian.",
|
||||
@ -362,6 +370,8 @@
|
||||
"dailyCheckNegativeHint6": "Going out",
|
||||
"dailyCheckNegativeHint6Description": "Forgot your umbrella and got caught in the rain",
|
||||
"happyBirthday": "Happy birthday, {}!",
|
||||
"celebrateMerryXmas": "Merry christmas, {}!",
|
||||
"celebrateNewYear": "Happy new year, {}!",
|
||||
"friendNew": "Add Friend",
|
||||
"friendRequests": "Friend Requests",
|
||||
"friendRequestsDescription": {
|
||||
@ -395,16 +405,18 @@
|
||||
"accountJoinedAt": "Joined at {}",
|
||||
"accountBirthday": "Born on {}",
|
||||
"accountBadge": "Badge",
|
||||
"badgeCompanyStaff": "Solsynth LLC Staff",
|
||||
"badgeCompanyStaff": "Solsynth Staff",
|
||||
"badgeSiteMigration": "Solar Network Native",
|
||||
"accountStatus": "Status",
|
||||
"accountStatusOnline": "Online",
|
||||
"accountStatusOffline": "Offline",
|
||||
"accountStatusLastSeen": "Last seen at {}",
|
||||
"postArticle": "Article on the Solar Network",
|
||||
"postStory": "Story on the Solar Network",
|
||||
"articleWrittenAt": "Written at {}",
|
||||
"articleEditedAt": "Edited at {}",
|
||||
"attachmentSaved": "Saved to album",
|
||||
"attachmentSavedDesktop": "Saved to Downloads folder",
|
||||
"openInAlbum": "Open in album",
|
||||
"postAbuseReport": "Report Post",
|
||||
"postAbuseReportDescription": "Report posts that violate our user agreement and community guidelines to help us improve the content on Solar Network. Please describe how this post violates the relevant rules. Do not include any sensitive information. We will process your report within 24 hours.",
|
||||
@ -431,5 +443,21 @@
|
||||
"serviceStatus": "Service Status",
|
||||
"termRelated": "Related Terms",
|
||||
"appDetails": "App Details",
|
||||
"postRecommendation": "Highlight Posts"
|
||||
"postRecommendation": "Highlight Posts",
|
||||
"publisherBlockHint": "Block {}",
|
||||
"publisherBlockHintDescription": "You are going to block this publisher's maintainer, this will also block publishers that run by the same user.",
|
||||
"userUnblocked": "{} has been unblocked.",
|
||||
"userBlocked": "{} has been blocked.",
|
||||
"postSharingViaPicture": "Capturing post as picture, please stand by...",
|
||||
"postImageShareReadMore": "Scan the QR code to read full post",
|
||||
"postImageShareAds": "Explore posts on the Solar Network",
|
||||
"postShare": "Share",
|
||||
"postShareImage": "Share via Image",
|
||||
"appInitializing": "Initializing",
|
||||
"poweredBy": "Powered by {}",
|
||||
"shareIntent": "Share",
|
||||
"shareIntentDescription": "What do you want to do with the content you are sharing?",
|
||||
"shareIntentPostStory": "Post a Story",
|
||||
"updateAvailable": "Update Available",
|
||||
"updateOngoing": "正在更新,请稍后..."
|
||||
}
|
||||
|
@ -112,6 +112,7 @@
|
||||
"one": "{} 点社会信用点",
|
||||
"other": "{} 点社会信用点"
|
||||
},
|
||||
"publisherAffiliatedBy": "隶属于 {}",
|
||||
"publisherRunBy": "由 {} 管理",
|
||||
"fieldPublisherBelongToRealm": "所属领域",
|
||||
"fieldPublisherBelongToRealmUnset": "未设置发布者所属领域",
|
||||
@ -187,6 +188,13 @@
|
||||
"settingsNetworkServerPreset": "预设的 HyperNet 服务器",
|
||||
"settingsNetworkServerPresetDescription": "你可以在旁边的列表中选择我们提供的预设 HyperNet 服务器地址。",
|
||||
"settingsNetworkServerSaved": "服务器地址已保存。",
|
||||
"settingsPerformance": "性能",
|
||||
"settingsImageQuality": "图片预览质量",
|
||||
"settingsImageQualityDescription": "设置图片预览质量,会影响图片解码速度。",
|
||||
"settingsImageQualityLowest": "极低",
|
||||
"settingsImageQualityLow": "低",
|
||||
"settingsImageQualityMedium": "中",
|
||||
"settingsImageQualityHigh": "高",
|
||||
"settingsMisc": "杂项",
|
||||
"settingsMiscAbout": "关于",
|
||||
"settingsMiscAboutDescription": "查看 Solian 的版本信息。",
|
||||
@ -360,6 +368,8 @@
|
||||
"dailyCheckNegativeHint6": "出门",
|
||||
"dailyCheckNegativeHint6Description": "忘带伞遇上大雨",
|
||||
"happyBirthday": "生日快乐,{}!",
|
||||
"celebrateMerryXmas": "圣诞快乐,{}!",
|
||||
"celebrateNewYear": "新年快乐,{}!",
|
||||
"friendNew": "添加好友",
|
||||
"friendRequests": "好友请求",
|
||||
"friendRequestsDescription": {
|
||||
@ -400,9 +410,11 @@
|
||||
"accountStatusOffline": "离线",
|
||||
"accountStatusLastSeen": "最后一次在 {} 上线",
|
||||
"postArticle": "Solar Network 上的文章",
|
||||
"postStory": "Solar Network 上的故事",
|
||||
"articleWrittenAt": "发表于 {}",
|
||||
"articleEditedAt": "编辑于 {}",
|
||||
"attachmentSaved": "已保存到相册",
|
||||
"attachmentSavedDesktop": "已保存到下载目录",
|
||||
"openInAlbum": "在相册中打开",
|
||||
"postAbuseReport": "检举帖子",
|
||||
"postAbuseReportDescription": "检举不符合我们用户协议以及社区准则的帖子,来帮助我们更好的维护 Solar Network 上的内容。请在下面描述该帖子如何违反我么的相关规定。请勿填写任何敏感信息。我们将会在 24 小时内处理您的检举。",
|
||||
@ -429,5 +441,21 @@
|
||||
"serviceStatus": "服务状态",
|
||||
"termRelated": "相关条款",
|
||||
"appDetails": "应用程序详情",
|
||||
"postRecommendation": "推荐帖子"
|
||||
"postRecommendation": "推荐帖子",
|
||||
"publisherBlockHint": "屏蔽 {}",
|
||||
"publisherBlockHintDescription": "你正要屏蔽此发布者的运营者,该操作也将屏蔽由同一用户运营的发布者。",
|
||||
"userUnblocked": "已解除屏蔽用户 {}",
|
||||
"userBlocked": "已屏蔽用户 {}",
|
||||
"postSharingViaPicture": "正在生成帖子截图,请稍等片刻……",
|
||||
"postImageShareReadMore": "扫描右侧 QRCode 查看全文",
|
||||
"postImageShareAds": "来 Solar Network 探索更多有趣帖子",
|
||||
"postShare": "分享",
|
||||
"postShareImage": "分享帖图",
|
||||
"appInitializing": "正在初始化",
|
||||
"poweredBy": "由 {} 提供支持",
|
||||
"shareIntent": "分享",
|
||||
"shareIntentDescription": "您想对您分享的内容做些什么?",
|
||||
"shareIntentPostStory": "发布动态",
|
||||
"updateAvailable": "检测到更新可用",
|
||||
"updateOngoing": "正在更新,请稍后……"
|
||||
}
|
||||
|
@ -112,6 +112,7 @@
|
||||
"one": "{} 點社會信用點",
|
||||
"other": "{} 點社會信用點"
|
||||
},
|
||||
"publisherAffiliatedBy": "隸屬於 {}",
|
||||
"publisherRunBy": "由 {} 管理",
|
||||
"fieldPublisherBelongToRealm": "所屬領域",
|
||||
"fieldPublisherBelongToRealmUnset": "未設置發佈者所屬領域",
|
||||
@ -187,6 +188,13 @@
|
||||
"settingsNetworkServerPreset": "預設的 HyperNet 服務器",
|
||||
"settingsNetworkServerPresetDescription": "你可以在旁邊的列表中選擇我們提供的預設 HyperNet 服務器地址。",
|
||||
"settingsNetworkServerSaved": "服務器地址已保存。",
|
||||
"settingsPerformance": "性能",
|
||||
"settingsImageQuality": "圖片預覽質量",
|
||||
"settingsImageQualityDescription": "設置圖片預覽質量,會影響圖片解碼速度。",
|
||||
"settingsImageQualityLowest": "極低",
|
||||
"settingsImageQualityLow": "低",
|
||||
"settingsImageQualityMedium": "中",
|
||||
"settingsImageQualityHigh": "高",
|
||||
"settingsMisc": "雜項",
|
||||
"settingsMiscAbout": "關於",
|
||||
"settingsMiscAboutDescription": "查看 Solian 的版本信息。",
|
||||
@ -400,9 +408,11 @@
|
||||
"accountStatusOffline": "離線",
|
||||
"accountStatusLastSeen": "最後一次在 {} 上線",
|
||||
"postArticle": "Solar Network 上的文章",
|
||||
"postStory": "Solar Network 上的故事",
|
||||
"articleWrittenAt": "發表於 {}",
|
||||
"articleEditedAt": "編輯於 {}",
|
||||
"attachmentSaved": "已保存到相冊",
|
||||
"attachmentSavedDesktop": "已保存到下載目錄",
|
||||
"openInAlbum": "在相冊中打開",
|
||||
"postAbuseReport": "檢舉帖子",
|
||||
"postAbuseReportDescription": "檢舉不符合我們用户協議以及社區準則的帖子,來幫助我們更好的維護 Solar Network 上的內容。請在下面描述該帖子如何違反我麼的相關規定。請勿填寫任何敏感信息。我們將會在 24 小時內處理您的檢舉。",
|
||||
@ -429,5 +439,19 @@
|
||||
"serviceStatus": "服務狀態",
|
||||
"termRelated": "相關條款",
|
||||
"appDetails": "應用程序詳情",
|
||||
"postRecommendation": "推薦帖子"
|
||||
"postRecommendation": "推薦帖子",
|
||||
"publisherBlockHint": "屏蔽 {}",
|
||||
"publisherBlockHintDescription": "你正要屏蔽此發佈者的運營者,該操作也將屏蔽由同一用户運營的發佈者。",
|
||||
"userUnblocked": "已解除屏蔽用户 {}",
|
||||
"userBlocked": "已屏蔽用户 {}",
|
||||
"postSharingViaPicture": "正在生成帖子截圖,請稍等片刻……",
|
||||
"postImageShareReadMore": "掃描右側 QRCode 查看全文",
|
||||
"postImageShareAds": "來 Solar Network 探索更多有趣帖子",
|
||||
"postShare": "分享",
|
||||
"postShareImage": "分享帖圖",
|
||||
"appInitializing": "正在初始化",
|
||||
"poweredBy": "由 {} 提供支持",
|
||||
"shareIntent": "分享",
|
||||
"shareIntentDescription": "您想對您分享的內容做些什麼?",
|
||||
"shareIntentPostStory": "發佈動態"
|
||||
}
|
||||
|
@ -112,6 +112,7 @@
|
||||
"one": "{} 點社會信用點",
|
||||
"other": "{} 點社會信用點"
|
||||
},
|
||||
"publisherAffiliatedBy": "隸屬於 {}",
|
||||
"publisherRunBy": "由 {} 管理",
|
||||
"fieldPublisherBelongToRealm": "所屬領域",
|
||||
"fieldPublisherBelongToRealmUnset": "未設定釋出者所屬領域",
|
||||
@ -187,6 +188,13 @@
|
||||
"settingsNetworkServerPreset": "預設的 HyperNet 伺服器",
|
||||
"settingsNetworkServerPresetDescription": "你可以在旁邊的列表中選擇我們提供的預設 HyperNet 伺服器地址。",
|
||||
"settingsNetworkServerSaved": "伺服器地址已儲存。",
|
||||
"settingsPerformance": "效能",
|
||||
"settingsImageQuality": "圖片預覽質量",
|
||||
"settingsImageQualityDescription": "設定圖片預覽質量,會影響圖片解碼速度。",
|
||||
"settingsImageQualityLowest": "極低",
|
||||
"settingsImageQualityLow": "低",
|
||||
"settingsImageQualityMedium": "中",
|
||||
"settingsImageQualityHigh": "高",
|
||||
"settingsMisc": "雜項",
|
||||
"settingsMiscAbout": "關於",
|
||||
"settingsMiscAboutDescription": "檢視 Solian 的版本資訊。",
|
||||
@ -400,9 +408,11 @@
|
||||
"accountStatusOffline": "離線",
|
||||
"accountStatusLastSeen": "最後一次在 {} 上線",
|
||||
"postArticle": "Solar Network 上的文章",
|
||||
"postStory": "Solar Network 上的故事",
|
||||
"articleWrittenAt": "發表於 {}",
|
||||
"articleEditedAt": "編輯於 {}",
|
||||
"attachmentSaved": "已儲存到相簿",
|
||||
"attachmentSavedDesktop": "已儲存到下載目錄",
|
||||
"openInAlbum": "在相簿中開啟",
|
||||
"postAbuseReport": "檢舉帖子",
|
||||
"postAbuseReportDescription": "檢舉不符合我們使用者協議以及社群準則的帖子,來幫助我們更好的維護 Solar Network 上的內容。請在下面描述該帖子如何違反我麼的相關規定。請勿填寫任何敏感資訊。我們將會在 24 小時內處理您的檢舉。",
|
||||
@ -429,5 +439,19 @@
|
||||
"serviceStatus": "服務狀態",
|
||||
"termRelated": "相關條款",
|
||||
"appDetails": "應用程式詳情",
|
||||
"postRecommendation": "推薦帖子"
|
||||
"postRecommendation": "推薦帖子",
|
||||
"publisherBlockHint": "遮蔽 {}",
|
||||
"publisherBlockHintDescription": "你正要遮蔽此釋出者的運營者,該操作也將遮蔽由同一使用者運營的釋出者。",
|
||||
"userUnblocked": "已解除遮蔽使用者 {}",
|
||||
"userBlocked": "已遮蔽使用者 {}",
|
||||
"postSharingViaPicture": "正在生成帖子截圖,請稍等片刻……",
|
||||
"postImageShareReadMore": "掃描右側 QRCode 檢視全文",
|
||||
"postImageShareAds": "來 Solar Network 探索更多有趣帖子",
|
||||
"postShare": "分享",
|
||||
"postShareImage": "分享帖圖",
|
||||
"appInitializing": "正在初始化",
|
||||
"poweredBy": "由 {} 提供支援",
|
||||
"shareIntent": "分享",
|
||||
"shareIntentDescription": "您想對您分享的內容做些什麼?",
|
||||
"shareIntentPostStory": "釋出動態"
|
||||
}
|
||||
|
22
ios/Podfile
22
ios/Podfile
@ -35,6 +35,28 @@ target 'Runner' do
|
||||
target 'RunnerTests' do
|
||||
inherit! :search_paths
|
||||
end
|
||||
|
||||
target 'SolarNotifyService' do
|
||||
inherit! :search_paths
|
||||
pod 'home_widget', :path => '.symlinks/plugins/home_widget/ios'
|
||||
|
||||
pod 'Kingfisher', '~> 8.0'
|
||||
pod 'Alamofire'
|
||||
end
|
||||
|
||||
target 'SolarWidgetExtension' do
|
||||
inherit! :search_paths
|
||||
use_frameworks!
|
||||
use_modular_headers!
|
||||
|
||||
pod 'home_widget', :path => '.symlinks/plugins/home_widget/ios'
|
||||
|
||||
pod 'Kingfisher', '~> 8.0'
|
||||
end
|
||||
|
||||
target 'SolarShare' do
|
||||
inherit! :search_paths
|
||||
end
|
||||
end
|
||||
|
||||
post_install do |installer|
|
||||
|
@ -1,11 +1,10 @@
|
||||
PODS:
|
||||
- Alamofire (5.10.2)
|
||||
- connectivity_plus (0.0.1):
|
||||
- Flutter
|
||||
- FlutterMacOS
|
||||
- croppy (0.0.1):
|
||||
- Flutter
|
||||
- cupertino_http (0.0.1):
|
||||
- Flutter
|
||||
- device_info_plus (0.0.1):
|
||||
- Flutter
|
||||
- DKImagePickerController/Core (4.3.9):
|
||||
@ -42,6 +41,8 @@ PODS:
|
||||
- file_picker (0.0.1):
|
||||
- DKImagePickerController/PhotoGallery
|
||||
- Flutter
|
||||
- file_saver (0.0.1):
|
||||
- Flutter
|
||||
- Firebase/Analytics (11.4.0):
|
||||
- Firebase/Core
|
||||
- Firebase/Core (11.4.0):
|
||||
@ -56,7 +57,7 @@ PODS:
|
||||
- Firebase/Analytics (= 11.4.0)
|
||||
- firebase_core
|
||||
- Flutter
|
||||
- firebase_core (3.8.1):
|
||||
- firebase_core (3.9.0):
|
||||
- Firebase/CoreOnly (= 11.4.0)
|
||||
- Flutter
|
||||
- firebase_messaging (15.1.6):
|
||||
@ -85,7 +86,7 @@ PODS:
|
||||
- FirebaseCoreInternal (~> 11.0)
|
||||
- GoogleUtilities/Environment (~> 8.0)
|
||||
- GoogleUtilities/Logger (~> 8.0)
|
||||
- FirebaseCoreInternal (11.5.0):
|
||||
- FirebaseCoreInternal (11.6.0):
|
||||
- "GoogleUtilities/NSData+zlib (~> 8.0)"
|
||||
- FirebaseInstallations (11.4.0):
|
||||
- FirebaseCore (~> 11.0)
|
||||
@ -163,10 +164,14 @@ PODS:
|
||||
- GoogleUtilities/UserDefaults (8.0.2):
|
||||
- GoogleUtilities/Logger
|
||||
- GoogleUtilities/Privacy
|
||||
- home_widget (0.0.1):
|
||||
- Flutter
|
||||
- image_picker_ios (0.0.1):
|
||||
- Flutter
|
||||
- livekit_client (2.3.1):
|
||||
- Kingfisher (8.1.3)
|
||||
- livekit_client (2.3.2):
|
||||
- Flutter
|
||||
- flutter_webrtc
|
||||
- WebRTC-SDK (= 125.6422.06)
|
||||
- media_kit_libs_ios_video (1.0.4):
|
||||
- Flutter
|
||||
@ -189,17 +194,14 @@ PODS:
|
||||
- permission_handler_apple (9.3.0):
|
||||
- Flutter
|
||||
- PromisesObjC (2.4.0)
|
||||
- receive_sharing_intent (1.8.1):
|
||||
- Flutter
|
||||
- SAMKeychain (1.5.3)
|
||||
- screen_brightness_ios (0.1.0):
|
||||
- Flutter
|
||||
- SDWebImage (5.20.0):
|
||||
- SDWebImage/Core (= 5.20.0)
|
||||
- SDWebImage/Core (5.20.0)
|
||||
- Sentry/HybridSDK (8.40.1)
|
||||
- sentry_flutter (8.10.1):
|
||||
- Flutter
|
||||
- FlutterMacOS
|
||||
- Sentry/HybridSDK (= 8.40.1)
|
||||
- share_plus (0.0.1):
|
||||
- Flutter
|
||||
- shared_preferences_foundation (0.0.1):
|
||||
@ -216,13 +218,16 @@ PODS:
|
||||
- wakelock_plus (0.0.1):
|
||||
- Flutter
|
||||
- WebRTC-SDK (125.6422.06)
|
||||
- workmanager (0.0.1):
|
||||
- Flutter
|
||||
|
||||
DEPENDENCIES:
|
||||
- Alamofire
|
||||
- connectivity_plus (from `.symlinks/plugins/connectivity_plus/darwin`)
|
||||
- croppy (from `.symlinks/plugins/croppy/ios`)
|
||||
- cupertino_http (from `.symlinks/plugins/cupertino_http/ios`)
|
||||
- device_info_plus (from `.symlinks/plugins/device_info_plus/ios`)
|
||||
- file_picker (from `.symlinks/plugins/file_picker/ios`)
|
||||
- file_saver (from `.symlinks/plugins/file_saver/ios`)
|
||||
- firebase_analytics (from `.symlinks/plugins/firebase_analytics/ios`)
|
||||
- firebase_core (from `.symlinks/plugins/firebase_core/ios`)
|
||||
- firebase_messaging (from `.symlinks/plugins/firebase_messaging/ios`)
|
||||
@ -231,7 +236,9 @@ DEPENDENCIES:
|
||||
- flutter_udid (from `.symlinks/plugins/flutter_udid/ios`)
|
||||
- flutter_webrtc (from `.symlinks/plugins/flutter_webrtc/ios`)
|
||||
- gal (from `.symlinks/plugins/gal/darwin`)
|
||||
- home_widget (from `.symlinks/plugins/home_widget/ios`)
|
||||
- image_picker_ios (from `.symlinks/plugins/image_picker_ios/ios`)
|
||||
- Kingfisher (~> 8.0)
|
||||
- livekit_client (from `.symlinks/plugins/livekit_client/ios`)
|
||||
- media_kit_libs_ios_video (from `.symlinks/plugins/media_kit_libs_ios_video/ios`)
|
||||
- media_kit_native_event_loop (from `.symlinks/plugins/media_kit_native_event_loop/ios`)
|
||||
@ -240,17 +247,19 @@ DEPENDENCIES:
|
||||
- pasteboard (from `.symlinks/plugins/pasteboard/ios`)
|
||||
- path_provider_foundation (from `.symlinks/plugins/path_provider_foundation/darwin`)
|
||||
- permission_handler_apple (from `.symlinks/plugins/permission_handler_apple/ios`)
|
||||
- receive_sharing_intent (from `.symlinks/plugins/receive_sharing_intent/ios`)
|
||||
- screen_brightness_ios (from `.symlinks/plugins/screen_brightness_ios/ios`)
|
||||
- sentry_flutter (from `.symlinks/plugins/sentry_flutter/ios`)
|
||||
- share_plus (from `.symlinks/plugins/share_plus/ios`)
|
||||
- shared_preferences_foundation (from `.symlinks/plugins/shared_preferences_foundation/darwin`)
|
||||
- sqflite_darwin (from `.symlinks/plugins/sqflite_darwin/darwin`)
|
||||
- url_launcher_ios (from `.symlinks/plugins/url_launcher_ios/ios`)
|
||||
- volume_controller (from `.symlinks/plugins/volume_controller/ios`)
|
||||
- wakelock_plus (from `.symlinks/plugins/wakelock_plus/ios`)
|
||||
- workmanager (from `.symlinks/plugins/workmanager/ios`)
|
||||
|
||||
SPEC REPOS:
|
||||
trunk:
|
||||
- Alamofire
|
||||
- DKImagePickerController
|
||||
- DKPhotoGallery
|
||||
- Firebase
|
||||
@ -262,11 +271,11 @@ SPEC REPOS:
|
||||
- GoogleAppMeasurement
|
||||
- GoogleDataTransport
|
||||
- GoogleUtilities
|
||||
- Kingfisher
|
||||
- nanopb
|
||||
- PromisesObjC
|
||||
- SAMKeychain
|
||||
- SDWebImage
|
||||
- Sentry
|
||||
- SwiftyGif
|
||||
- WebRTC-SDK
|
||||
|
||||
@ -275,12 +284,12 @@ EXTERNAL SOURCES:
|
||||
:path: ".symlinks/plugins/connectivity_plus/darwin"
|
||||
croppy:
|
||||
:path: ".symlinks/plugins/croppy/ios"
|
||||
cupertino_http:
|
||||
:path: ".symlinks/plugins/cupertino_http/ios"
|
||||
device_info_plus:
|
||||
:path: ".symlinks/plugins/device_info_plus/ios"
|
||||
file_picker:
|
||||
:path: ".symlinks/plugins/file_picker/ios"
|
||||
file_saver:
|
||||
:path: ".symlinks/plugins/file_saver/ios"
|
||||
firebase_analytics:
|
||||
:path: ".symlinks/plugins/firebase_analytics/ios"
|
||||
firebase_core:
|
||||
@ -297,6 +306,8 @@ EXTERNAL SOURCES:
|
||||
:path: ".symlinks/plugins/flutter_webrtc/ios"
|
||||
gal:
|
||||
:path: ".symlinks/plugins/gal/darwin"
|
||||
home_widget:
|
||||
:path: ".symlinks/plugins/home_widget/ios"
|
||||
image_picker_ios:
|
||||
:path: ".symlinks/plugins/image_picker_ios/ios"
|
||||
livekit_client:
|
||||
@ -315,10 +326,10 @@ EXTERNAL SOURCES:
|
||||
:path: ".symlinks/plugins/path_provider_foundation/darwin"
|
||||
permission_handler_apple:
|
||||
:path: ".symlinks/plugins/permission_handler_apple/ios"
|
||||
receive_sharing_intent:
|
||||
:path: ".symlinks/plugins/receive_sharing_intent/ios"
|
||||
screen_brightness_ios:
|
||||
:path: ".symlinks/plugins/screen_brightness_ios/ios"
|
||||
sentry_flutter:
|
||||
:path: ".symlinks/plugins/sentry_flutter/ios"
|
||||
share_plus:
|
||||
:path: ".symlinks/plugins/share_plus/ios"
|
||||
shared_preferences_foundation:
|
||||
@ -331,34 +342,39 @@ EXTERNAL SOURCES:
|
||||
:path: ".symlinks/plugins/volume_controller/ios"
|
||||
wakelock_plus:
|
||||
:path: ".symlinks/plugins/wakelock_plus/ios"
|
||||
workmanager:
|
||||
:path: ".symlinks/plugins/workmanager/ios"
|
||||
|
||||
SPEC CHECKSUMS:
|
||||
connectivity_plus: 4c41c08fc6d7c91f63bc7aec70ffe3730b04f563
|
||||
Alamofire: 7193b3b92c74a07f85569e1a6c4f4237291e7496
|
||||
connectivity_plus: 18382e7311ba19efcaee94442b23b32507b20695
|
||||
croppy: b6199bc8d56bd2e03cc11609d1c47ad9875c1321
|
||||
cupertino_http: 1a3a0f163c1b26e7f1a293b33d476e0fde7a64ec
|
||||
device_info_plus: bf2e3232933866d73fe290f2942f2156cdd10342
|
||||
DKImagePickerController: 946cec48c7873164274ecc4624d19e3da4c1ef3c
|
||||
DKPhotoGallery: b3834fecb755ee09a593d7c9e389d8b5d6deed60
|
||||
file_picker: 09aa5ec1ab24135ccd7a1621c46c84134bfd6655
|
||||
file_saver: 503e386464dbe118f630e17b4c2e1190fa0cf808
|
||||
Firebase: cf1b19f21410b029b6786a54e9764a0cacad3c99
|
||||
firebase_analytics: 2815af29d49c1a994652abd37a5b001a88bc7b75
|
||||
firebase_core: 418aed674e9a0b8b6088aec16cde82a811f6261f
|
||||
firebase_core: b62a5080210edad3f2934314a8b2c6f5124e8e10
|
||||
firebase_messaging: 98619a0572d82cfb3668e78859ba9f1110e268c9
|
||||
FirebaseAnalytics: 3feef9ae8733c567866342a1000691baaa7cad49
|
||||
FirebaseCore: e0510f1523bc0eb21653cac00792e1e2bd6f1771
|
||||
FirebaseCoreInternal: f47dd28ae7782e6a4738aad3106071a8fe0af604
|
||||
FirebaseCoreInternal: d98ab91e2d80a56d7b246856a8885443b302c0c2
|
||||
FirebaseInstallations: 6ef4a1c7eb2a61ee1f74727d7f6ce2e72acf1414
|
||||
FirebaseMessaging: f8a160d99c2c2e5babbbcc90c4a3e15db036aee2
|
||||
Flutter: e0871f40cf51350855a761d2e70bf5af5b9b5de7
|
||||
flutter_native_splash: e8a1e01082d97a8099d973f919f57904c925008a
|
||||
flutter_udid: a2482c67a61b9c806ef59dd82ed8d007f1b7ac04
|
||||
flutter_webrtc: 1a53bd24f97bcfeff512f13699e721897f261563
|
||||
gal: 61e868295d28fe67ffa297fae6dacebf56fd53e1
|
||||
gal: 6a522c75909f1244732d4596d11d6a2f86ff37a5
|
||||
GoogleAppMeasurement: 987769c4ca6b968f2479fbcc9fe3ce34af454b8e
|
||||
GoogleDataTransport: aae35b7ea0c09004c3797d53c8c41f66f219d6a7
|
||||
GoogleUtilities: 26a3abef001b6533cf678d3eb38fd3f614b7872d
|
||||
home_widget: 0434835a4c9a75704264feff6be17ea40e0f0d57
|
||||
image_picker_ios: c560581cceedb403a6ff17f2f816d7fea1421fc1
|
||||
livekit_client: dbb906ef427fe96dde5854471c3dda0a50cc15f9
|
||||
Kingfisher: f2af9028b16baf9dc6c07c570072bc41cbf009ef
|
||||
livekit_client: 6108dad8b77db3142bafd4c630f471d0a54335cd
|
||||
media_kit_libs_ios_video: a5fe24bc7875ccd6378a0978c13185e1344651c1
|
||||
media_kit_native_event_loop: e6b2ab20cf0746eb1c33be961fcf79667304fa2a
|
||||
media_kit_video: 5da63f157170e5bf303bf85453b7ef6971218a2e
|
||||
@ -368,20 +384,20 @@ SPEC CHECKSUMS:
|
||||
path_provider_foundation: 2b6b4c569c0fb62ec74538f866245ac84301af46
|
||||
permission_handler_apple: 9878588469a2b0d0fc1e048d9f43605f92e6cec2
|
||||
PromisesObjC: f5707f49cb48b9636751c5b2e7d227e43fba9f47
|
||||
receive_sharing_intent: 79c848f5b045674ad60b9fea3bafea59962ad2c1
|
||||
SAMKeychain: 483e1c9f32984d50ca961e26818a534283b4cd5c
|
||||
screen_brightness_ios: 715ca807df953bf676d339f11464e438143ee625
|
||||
SDWebImage: 73c6079366fea25fa4bb9640d5fb58f0893facd8
|
||||
Sentry: e9215d7b17f7902692b4f8700e061e4f853e3521
|
||||
sentry_flutter: 927eed60d66951d1b0f1db37fe94ff5cb7c80231
|
||||
share_plus: 8b6f8b3447e494cca5317c8c3073de39b3600d1f
|
||||
shared_preferences_foundation: fcdcbc04712aee1108ac7fda236f363274528f78
|
||||
sqflite_darwin: 5a7236e3b501866c1c9befc6771dfd73ffb8702d
|
||||
SwiftyGif: 706c60cf65fa2bc5ee0313beece843c8eb8194d4
|
||||
url_launcher_ios: 5334b05cef931de560670eeae103fd3e431ac3fe
|
||||
volume_controller: 531ddf792994285c9b17f9d8a7e4dcdd29b3eae9
|
||||
wakelock_plus: 78ec7c5b202cab7761af8e2b2b3d0671be6c4ae1
|
||||
wakelock_plus: 373cfe59b235a6dd5837d0fb88791d2f13a90d56
|
||||
WebRTC-SDK: 79942c006ea64f6fb48d7da8a4786dfc820bc1db
|
||||
workmanager: 0afdcf5628bbde6924c21af7836fed07b42e30e6
|
||||
|
||||
PODFILE CHECKSUM: d2bdaa1cc7915e14cf47235c34a21fcb07b00390
|
||||
PODFILE CHECKSUM: 9b244e02f87527430136c8d21cbdcf1cd586b6bc
|
||||
|
||||
COCOAPODS: 1.16.2
|
||||
|
@ -3,7 +3,7 @@
|
||||
archiveVersion = 1;
|
||||
classes = {
|
||||
};
|
||||
objectVersion = 70;
|
||||
objectVersion = 54;
|
||||
objects = {
|
||||
|
||||
/* Begin PBXBuildFile section */
|
||||
@ -11,6 +11,11 @@
|
||||
1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */ = {isa = PBXBuildFile; fileRef = 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */; };
|
||||
331C808B294A63AB00263BE5 /* RunnerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 331C807B294A618700263BE5 /* RunnerTests.swift */; };
|
||||
3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; };
|
||||
738C1EAC2D0D76A400A215F3 /* WidgetKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 731B7B6B2D0D6CE000CEB9B7 /* WidgetKit.framework */; };
|
||||
738C1EAD2D0D76A400A215F3 /* SwiftUI.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 731B7B6D2D0D6CE000CEB9B7 /* SwiftUI.framework */; };
|
||||
738C1EB82D0D76A500A215F3 /* SolarWidgetExtension.appex in Embed Foundation Extensions */ = {isa = PBXBuildFile; fileRef = 738C1EAB2D0D76A400A215F3 /* SolarWidgetExtension.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; };
|
||||
7396A3522D16BD890095F4A8 /* NotifyDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7396A3512D16BD890095F4A8 /* NotifyDelegate.swift */; };
|
||||
73B7746E2D0E869200A789CE /* SolarShare.appex in Embed Foundation Extensions */ = {isa = PBXBuildFile; fileRef = 73B774642D0E869200A789CE /* SolarShare.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; };
|
||||
73DA8A012D05C7620024A03E /* SolarNotifyService.appex in Embed Foundation Extensions */ = {isa = PBXBuildFile; fileRef = 73DA89FA2D05C7620024A03E /* SolarNotifyService.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; };
|
||||
74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74858FAE1ED2DC5600515810 /* AppDelegate.swift */; };
|
||||
8CD0929C27BC410DD5056EAB /* GoogleService-Info.plist in Resources */ = {isa = PBXBuildFile; fileRef = A2C24C5238FAC44EA2CCF738 /* GoogleService-Info.plist */; };
|
||||
@ -18,6 +23,9 @@
|
||||
97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; };
|
||||
97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */; };
|
||||
CED170BFB6A72CDDAC285637 /* Pods_Runner.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = EDF483E994343CDFBF9BA347 /* Pods_Runner.framework */; };
|
||||
D5125CF12F159F0B8BC7641D /* Pods_SolarNotifyService.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 02469D286F48D84300484B1E /* Pods_SolarNotifyService.framework */; };
|
||||
D962B51F682FBDEC00AC7281 /* Pods_SolarWidgetExtension.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 7B1A159F5551E280D0EFC129 /* Pods_SolarWidgetExtension.framework */; };
|
||||
F51C4E3C8FA95426C91FC0A4 /* Pods_SolarShare.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 16F41E029731EA30268EDE2A /* Pods_SolarShare.framework */; };
|
||||
/* End PBXBuildFile section */
|
||||
|
||||
/* Begin PBXContainerItemProxy section */
|
||||
@ -28,6 +36,20 @@
|
||||
remoteGlobalIDString = 97C146ED1CF9000F007C117D;
|
||||
remoteInfo = Runner;
|
||||
};
|
||||
738C1EB62D0D76A500A215F3 /* PBXContainerItemProxy */ = {
|
||||
isa = PBXContainerItemProxy;
|
||||
containerPortal = 97C146E61CF9000F007C117D /* Project object */;
|
||||
proxyType = 1;
|
||||
remoteGlobalIDString = 738C1EAA2D0D76A400A215F3;
|
||||
remoteInfo = SolarWidgetExtension;
|
||||
};
|
||||
73B7746C2D0E869200A789CE /* PBXContainerItemProxy */ = {
|
||||
isa = PBXContainerItemProxy;
|
||||
containerPortal = 97C146E61CF9000F007C117D /* Project object */;
|
||||
proxyType = 1;
|
||||
remoteGlobalIDString = 73B774632D0E869200A789CE;
|
||||
remoteInfo = SolarShare;
|
||||
};
|
||||
73DA89FF2D05C7620024A03E /* PBXContainerItemProxy */ = {
|
||||
isa = PBXContainerItemProxy;
|
||||
containerPortal = 97C146E61CF9000F007C117D /* Project object */;
|
||||
@ -40,10 +62,12 @@
|
||||
/* Begin PBXCopyFilesBuildPhase section */
|
||||
73DA8A022D05C7620024A03E /* Embed Foundation Extensions */ = {
|
||||
isa = PBXCopyFilesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
buildActionMask = 12;
|
||||
dstPath = "";
|
||||
dstSubfolderSpec = 13;
|
||||
files = (
|
||||
738C1EB82D0D76A500A215F3 /* SolarWidgetExtension.appex in Embed Foundation Extensions */,
|
||||
73B7746E2D0E869200A789CE /* SolarShare.appex in Embed Foundation Extensions */,
|
||||
73DA8A012D05C7620024A03E /* SolarNotifyService.appex in Embed Foundation Extensions */,
|
||||
);
|
||||
name = "Embed Foundation Extensions";
|
||||
@ -62,22 +86,40 @@
|
||||
/* End PBXCopyFilesBuildPhase section */
|
||||
|
||||
/* Begin PBXFileReference section */
|
||||
02469D286F48D84300484B1E /* Pods_SolarNotifyService.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_SolarNotifyService.framework; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
1077EFD9ACF793E9DA5D5B63 /* Pods-Runner-SolarNotifyService.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner-SolarNotifyService.release.xcconfig"; path = "Target Support Files/Pods-Runner-SolarNotifyService/Pods-Runner-SolarNotifyService.release.xcconfig"; sourceTree = "<group>"; };
|
||||
1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GeneratedPluginRegistrant.h; sourceTree = "<group>"; };
|
||||
1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GeneratedPluginRegistrant.m; sourceTree = "<group>"; };
|
||||
16F41E029731EA30268EDE2A /* Pods_SolarShare.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_SolarShare.framework; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
2134F3903A0E8EB8CC2670BE /* Pods-SolarWidgetExtension.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-SolarWidgetExtension.debug.xcconfig"; path = "Target Support Files/Pods-SolarWidgetExtension/Pods-SolarWidgetExtension.debug.xcconfig"; sourceTree = "<group>"; };
|
||||
26CC8DE2338798EAB472B62D /* Pods_RunnerTests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_RunnerTests.framework; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
2DA1B873D39B9FD33298BBCE /* Pods-SolarShare.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-SolarShare.profile.xcconfig"; path = "Target Support Files/Pods-SolarShare/Pods-SolarShare.profile.xcconfig"; sourceTree = "<group>"; };
|
||||
331C807B294A618700263BE5 /* RunnerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RunnerTests.swift; sourceTree = "<group>"; };
|
||||
331C8081294A63A400263BE5 /* RunnerTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = RunnerTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = "<group>"; };
|
||||
40B53769EB464E54DACA7CE4 /* Pods-RunnerTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.debug.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.debug.xcconfig"; sourceTree = "<group>"; };
|
||||
430F31F96B82659CBEAD4326 /* Pods-Runner-SolarWidgetExtension.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner-SolarWidgetExtension.profile.xcconfig"; path = "Target Support Files/Pods-Runner-SolarWidgetExtension/Pods-Runner-SolarWidgetExtension.profile.xcconfig"; sourceTree = "<group>"; };
|
||||
48AE73F9950AF4FB02B5E9F4 /* Pods-Runner.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.debug.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"; sourceTree = "<group>"; };
|
||||
4A2F84B6033057E3BD2C7CB8 /* Pods-Runner.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.release.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"; sourceTree = "<group>"; };
|
||||
4CBF45ABD292EE527D0A4D1E /* Pods-SolarNotifyService.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-SolarNotifyService.profile.xcconfig"; path = "Target Support Files/Pods-SolarNotifyService/Pods-SolarNotifyService.profile.xcconfig"; sourceTree = "<group>"; };
|
||||
5922A50B1231B06B92E31F20 /* Pods-SolarShare.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-SolarShare.debug.xcconfig"; path = "Target Support Files/Pods-SolarShare/Pods-SolarShare.debug.xcconfig"; sourceTree = "<group>"; };
|
||||
64FBE78F9C282712818D6D95 /* Pods-RunnerTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.release.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.release.xcconfig"; sourceTree = "<group>"; };
|
||||
6618E2E3015264643175B43D /* Pods-SolarWidgetExtension.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-SolarWidgetExtension.release.xcconfig"; path = "Target Support Files/Pods-SolarWidgetExtension/Pods-SolarWidgetExtension.release.xcconfig"; sourceTree = "<group>"; };
|
||||
72E9279EFA6DAC00BBAC493C /* Pods-Runner.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.profile.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.profile.xcconfig"; sourceTree = "<group>"; };
|
||||
73111C212CEE3D5E004CF4B3 /* Runner.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = Runner.entitlements; sourceTree = "<group>"; };
|
||||
731B7B6B2D0D6CE000CEB9B7 /* WidgetKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = WidgetKit.framework; path = System/Library/Frameworks/WidgetKit.framework; sourceTree = SDKROOT; };
|
||||
731B7B6D2D0D6CE000CEB9B7 /* SwiftUI.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = SwiftUI.framework; path = System/Library/Frameworks/SwiftUI.framework; sourceTree = SDKROOT; };
|
||||
738C1EAB2D0D76A400A215F3 /* SolarWidgetExtension.appex */ = {isa = PBXFileReference; explicitFileType = "wrapper.app-extension"; includeInIndex = 0; path = SolarWidgetExtension.appex; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
738C1F132D0D7DDC00A215F3 /* SolarWidgetExtension.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = SolarWidgetExtension.entitlements; sourceTree = "<group>"; };
|
||||
7396A3512D16BD890095F4A8 /* NotifyDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotifyDelegate.swift; sourceTree = "<group>"; };
|
||||
73B774642D0E869200A789CE /* SolarShare.appex */ = {isa = PBXFileReference; explicitFileType = "wrapper.app-extension"; includeInIndex = 0; path = SolarShare.appex; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
73DA89FA2D05C7620024A03E /* SolarNotifyService.appex */ = {isa = PBXFileReference; explicitFileType = "wrapper.app-extension"; includeInIndex = 0; path = SolarNotifyService.appex; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Runner-Bridging-Header.h"; sourceTree = "<group>"; };
|
||||
74858FAE1ED2DC5600515810 /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = "<group>"; };
|
||||
7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Release.xcconfig; path = Flutter/Release.xcconfig; sourceTree = "<group>"; };
|
||||
7B1A159F5551E280D0EFC129 /* Pods_SolarWidgetExtension.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_SolarWidgetExtension.framework; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
8E44A071621D5CAF864FB2F1 /* Pods-Runner-SolarNotifyService.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner-SolarNotifyService.debug.xcconfig"; path = "Target Support Files/Pods-Runner-SolarNotifyService/Pods-Runner-SolarNotifyService.debug.xcconfig"; sourceTree = "<group>"; };
|
||||
931FBE9EDB99B3AD8B1FFB00 /* Pods-Runner-SolarWidgetExtension.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner-SolarWidgetExtension.release.xcconfig"; path = "Target Support Files/Pods-Runner-SolarWidgetExtension/Pods-Runner-SolarWidgetExtension.release.xcconfig"; sourceTree = "<group>"; };
|
||||
96081771773FA019A97CCC3F /* Pods-RunnerTests.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.profile.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.profile.xcconfig"; sourceTree = "<group>"; };
|
||||
9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Debug.xcconfig; path = Flutter/Debug.xcconfig; sourceTree = "<group>"; };
|
||||
9740EEB31CF90195004384FC /* Generated.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Generated.xcconfig; path = Flutter/Generated.xcconfig; sourceTree = "<group>"; };
|
||||
@ -87,11 +129,53 @@
|
||||
97C147001CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = "<group>"; };
|
||||
97C147021CF9000F007C117D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
|
||||
A2C24C5238FAC44EA2CCF738 /* GoogleService-Info.plist */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.plist.xml; name = "GoogleService-Info.plist"; path = "Runner/GoogleService-Info.plist"; sourceTree = "<group>"; };
|
||||
B1763F1D7318A2745CA7EDFE /* Pods-SolarShare.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-SolarShare.release.xcconfig"; path = "Target Support Files/Pods-SolarShare/Pods-SolarShare.release.xcconfig"; sourceTree = "<group>"; };
|
||||
B4550C68292419CDC580808B /* Pods-Runner-SolarNotifyService.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner-SolarNotifyService.profile.xcconfig"; path = "Target Support Files/Pods-Runner-SolarNotifyService/Pods-Runner-SolarNotifyService.profile.xcconfig"; sourceTree = "<group>"; };
|
||||
BCE0C4086B776A27B202B373 /* Pods-SolarWidgetExtension.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-SolarWidgetExtension.profile.xcconfig"; path = "Target Support Files/Pods-SolarWidgetExtension/Pods-SolarWidgetExtension.profile.xcconfig"; sourceTree = "<group>"; };
|
||||
BFF3B436D74FA8CBFFE34A27 /* Pods-Runner-SolarWidgetExtension.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner-SolarWidgetExtension.debug.xcconfig"; path = "Target Support Files/Pods-Runner-SolarWidgetExtension/Pods-Runner-SolarWidgetExtension.debug.xcconfig"; sourceTree = "<group>"; };
|
||||
D7E1FA77FDA53439DB2C0E75 /* Pods-SolarNotifyService.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-SolarNotifyService.release.xcconfig"; path = "Target Support Files/Pods-SolarNotifyService/Pods-SolarNotifyService.release.xcconfig"; sourceTree = "<group>"; };
|
||||
D96D1DB4ED46A2640C1B9D34 /* Pods-SolarNotifyService.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-SolarNotifyService.debug.xcconfig"; path = "Target Support Files/Pods-SolarNotifyService/Pods-SolarNotifyService.debug.xcconfig"; sourceTree = "<group>"; };
|
||||
EDF483E994343CDFBF9BA347 /* Pods_Runner.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Runner.framework; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
/* End PBXFileReference section */
|
||||
|
||||
/* Begin PBXFileSystemSynchronizedBuildFileExceptionSet section */
|
||||
73DA8A062D05C7620024A03E /* PBXFileSystemSynchronizedBuildFileExceptionSet */ = {
|
||||
738C1EB92D0D76A500A215F3 /* Exceptions for "SolarWidget" folder in "SolarWidgetExtension" target */ = {
|
||||
isa = PBXFileSystemSynchronizedBuildFileExceptionSet;
|
||||
membershipExceptions = (
|
||||
Info.plist,
|
||||
);
|
||||
target = 738C1EAA2D0D76A400A215F3 /* SolarWidgetExtension */;
|
||||
};
|
||||
738C1F512D0D91D000A215F3 /* Exceptions for "Data" folder in "SolarWidgetExtension" target */ = {
|
||||
isa = PBXFileSystemSynchronizedBuildFileExceptionSet;
|
||||
membershipExceptions = (
|
||||
Post.swift,
|
||||
User.swift,
|
||||
);
|
||||
target = 738C1EAA2D0D76A400A215F3 /* SolarWidgetExtension */;
|
||||
};
|
||||
73B774722D0E869200A789CE /* Exceptions for "SolarShare" folder in "SolarShare" target */ = {
|
||||
isa = PBXFileSystemSynchronizedBuildFileExceptionSet;
|
||||
membershipExceptions = (
|
||||
Info.plist,
|
||||
);
|
||||
target = 73B774632D0E869200A789CE /* SolarShare */;
|
||||
};
|
||||
73BC73712D0DDF6300956BE0 /* Exceptions for "Service" folder in "SolarNotifyService" target */ = {
|
||||
isa = PBXFileSystemSynchronizedBuildFileExceptionSet;
|
||||
membershipExceptions = (
|
||||
Attachment.swift,
|
||||
);
|
||||
target = 73DA89F92D05C7620024A03E /* SolarNotifyService */;
|
||||
};
|
||||
73BC73722D0DDF6300956BE0 /* Exceptions for "Service" folder in "SolarWidgetExtension" target */ = {
|
||||
isa = PBXFileSystemSynchronizedBuildFileExceptionSet;
|
||||
membershipExceptions = (
|
||||
Attachment.swift,
|
||||
);
|
||||
target = 738C1EAA2D0D76A400A215F3 /* SolarWidgetExtension */;
|
||||
};
|
||||
73DA8A062D05C7620024A03E /* Exceptions for "SolarNotifyService" folder in "SolarNotifyService" target */ = {
|
||||
isa = PBXFileSystemSynchronizedBuildFileExceptionSet;
|
||||
membershipExceptions = (
|
||||
Info.plist,
|
||||
@ -101,14 +185,73 @@
|
||||
/* End PBXFileSystemSynchronizedBuildFileExceptionSet section */
|
||||
|
||||
/* Begin PBXFileSystemSynchronizedRootGroup section */
|
||||
73DA89FB2D05C7620024A03E /* SolarNotifyService */ = {isa = PBXFileSystemSynchronizedRootGroup; exceptions = (73DA8A062D05C7620024A03E /* PBXFileSystemSynchronizedBuildFileExceptionSet */, ); explicitFileTypes = {}; explicitFolders = (); path = SolarNotifyService; sourceTree = "<group>"; };
|
||||
738C1EAE2D0D76A400A215F3 /* SolarWidget */ = {
|
||||
isa = PBXFileSystemSynchronizedRootGroup;
|
||||
exceptions = (
|
||||
738C1EB92D0D76A500A215F3 /* Exceptions for "SolarWidget" folder in "SolarWidgetExtension" target */,
|
||||
);
|
||||
path = SolarWidget;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
738C1F4F2D0D91CC00A215F3 /* Data */ = {
|
||||
isa = PBXFileSystemSynchronizedRootGroup;
|
||||
exceptions = (
|
||||
738C1F512D0D91D000A215F3 /* Exceptions for "Data" folder in "SolarWidgetExtension" target */,
|
||||
);
|
||||
path = Data;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
73B774652D0E869200A789CE /* SolarShare */ = {
|
||||
isa = PBXFileSystemSynchronizedRootGroup;
|
||||
exceptions = (
|
||||
73B774722D0E869200A789CE /* Exceptions for "SolarShare" folder in "SolarShare" target */,
|
||||
);
|
||||
path = SolarShare;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
73BC736C2D0DDF5600956BE0 /* Service */ = {
|
||||
isa = PBXFileSystemSynchronizedRootGroup;
|
||||
exceptions = (
|
||||
73BC73712D0DDF6300956BE0 /* Exceptions for "Service" folder in "SolarNotifyService" target */,
|
||||
73BC73722D0DDF6300956BE0 /* Exceptions for "Service" folder in "SolarWidgetExtension" target */,
|
||||
);
|
||||
path = Service;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
73DA89FB2D05C7620024A03E /* SolarNotifyService */ = {
|
||||
isa = PBXFileSystemSynchronizedRootGroup;
|
||||
exceptions = (
|
||||
73DA8A062D05C7620024A03E /* Exceptions for "SolarNotifyService" folder in "SolarNotifyService" target */,
|
||||
);
|
||||
path = SolarNotifyService;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
/* End PBXFileSystemSynchronizedRootGroup section */
|
||||
|
||||
/* Begin PBXFrameworksBuildPhase section */
|
||||
738C1EA82D0D76A400A215F3 /* Frameworks */ = {
|
||||
isa = PBXFrameworksBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
738C1EAD2D0D76A400A215F3 /* SwiftUI.framework in Frameworks */,
|
||||
738C1EAC2D0D76A400A215F3 /* WidgetKit.framework in Frameworks */,
|
||||
D962B51F682FBDEC00AC7281 /* Pods_SolarWidgetExtension.framework in Frameworks */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
73B774612D0E869200A789CE /* Frameworks */ = {
|
||||
isa = PBXFrameworksBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
F51C4E3C8FA95426C91FC0A4 /* Pods_SolarShare.framework in Frameworks */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
73DA89F72D05C7620024A03E /* Frameworks */ = {
|
||||
isa = PBXFrameworksBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
D5125CF12F159F0B8BC7641D /* Pods_SolarNotifyService.framework in Frameworks */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
@ -136,6 +279,11 @@
|
||||
children = (
|
||||
EDF483E994343CDFBF9BA347 /* Pods_Runner.framework */,
|
||||
26CC8DE2338798EAB472B62D /* Pods_RunnerTests.framework */,
|
||||
731B7B6B2D0D6CE000CEB9B7 /* WidgetKit.framework */,
|
||||
731B7B6D2D0D6CE000CEB9B7 /* SwiftUI.framework */,
|
||||
16F41E029731EA30268EDE2A /* Pods_SolarShare.framework */,
|
||||
02469D286F48D84300484B1E /* Pods_SolarNotifyService.framework */,
|
||||
7B1A159F5551E280D0EFC129 /* Pods_SolarWidgetExtension.framework */,
|
||||
);
|
||||
name = Frameworks;
|
||||
sourceTree = "<group>";
|
||||
@ -162,9 +310,12 @@
|
||||
97C146E51CF9000F007C117D = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
738C1F132D0D7DDC00A215F3 /* SolarWidgetExtension.entitlements */,
|
||||
9740EEB11CF90186004384FC /* Flutter */,
|
||||
97C146F01CF9000F007C117D /* Runner */,
|
||||
73DA89FB2D05C7620024A03E /* SolarNotifyService */,
|
||||
738C1EAE2D0D76A400A215F3 /* SolarWidget */,
|
||||
73B774652D0E869200A789CE /* SolarShare */,
|
||||
97C146EF1CF9000F007C117D /* Products */,
|
||||
331C8082294A63A400263BE5 /* RunnerTests */,
|
||||
F5165E3BD1F2519F85CD4BE2 /* Pods */,
|
||||
@ -179,6 +330,8 @@
|
||||
97C146EE1CF9000F007C117D /* Runner.app */,
|
||||
331C8081294A63A400263BE5 /* RunnerTests.xctest */,
|
||||
73DA89FA2D05C7620024A03E /* SolarNotifyService.appex */,
|
||||
738C1EAB2D0D76A400A215F3 /* SolarWidgetExtension.appex */,
|
||||
73B774642D0E869200A789CE /* SolarShare.appex */,
|
||||
);
|
||||
name = Products;
|
||||
sourceTree = "<group>";
|
||||
@ -186,6 +339,8 @@
|
||||
97C146F01CF9000F007C117D /* Runner */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
73BC736C2D0DDF5600956BE0 /* Service */,
|
||||
738C1F4F2D0D91CC00A215F3 /* Data */,
|
||||
73111C212CEE3D5E004CF4B3 /* Runner.entitlements */,
|
||||
97C146FA1CF9000F007C117D /* Main.storyboard */,
|
||||
97C146FD1CF9000F007C117D /* Assets.xcassets */,
|
||||
@ -195,6 +350,7 @@
|
||||
1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */,
|
||||
74858FAE1ED2DC5600515810 /* AppDelegate.swift */,
|
||||
74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */,
|
||||
7396A3512D16BD890095F4A8 /* NotifyDelegate.swift */,
|
||||
);
|
||||
path = Runner;
|
||||
sourceTree = "<group>";
|
||||
@ -208,6 +364,21 @@
|
||||
40B53769EB464E54DACA7CE4 /* Pods-RunnerTests.debug.xcconfig */,
|
||||
64FBE78F9C282712818D6D95 /* Pods-RunnerTests.release.xcconfig */,
|
||||
96081771773FA019A97CCC3F /* Pods-RunnerTests.profile.xcconfig */,
|
||||
5922A50B1231B06B92E31F20 /* Pods-SolarShare.debug.xcconfig */,
|
||||
B1763F1D7318A2745CA7EDFE /* Pods-SolarShare.release.xcconfig */,
|
||||
2DA1B873D39B9FD33298BBCE /* Pods-SolarShare.profile.xcconfig */,
|
||||
2134F3903A0E8EB8CC2670BE /* Pods-SolarWidgetExtension.debug.xcconfig */,
|
||||
6618E2E3015264643175B43D /* Pods-SolarWidgetExtension.release.xcconfig */,
|
||||
BCE0C4086B776A27B202B373 /* Pods-SolarWidgetExtension.profile.xcconfig */,
|
||||
D96D1DB4ED46A2640C1B9D34 /* Pods-SolarNotifyService.debug.xcconfig */,
|
||||
D7E1FA77FDA53439DB2C0E75 /* Pods-SolarNotifyService.release.xcconfig */,
|
||||
4CBF45ABD292EE527D0A4D1E /* Pods-SolarNotifyService.profile.xcconfig */,
|
||||
8E44A071621D5CAF864FB2F1 /* Pods-Runner-SolarNotifyService.debug.xcconfig */,
|
||||
1077EFD9ACF793E9DA5D5B63 /* Pods-Runner-SolarNotifyService.release.xcconfig */,
|
||||
B4550C68292419CDC580808B /* Pods-Runner-SolarNotifyService.profile.xcconfig */,
|
||||
BFF3B436D74FA8CBFFE34A27 /* Pods-Runner-SolarWidgetExtension.debug.xcconfig */,
|
||||
931FBE9EDB99B3AD8B1FFB00 /* Pods-Runner-SolarWidgetExtension.release.xcconfig */,
|
||||
430F31F96B82659CBEAD4326 /* Pods-Runner-SolarWidgetExtension.profile.xcconfig */,
|
||||
);
|
||||
path = Pods;
|
||||
sourceTree = "<group>";
|
||||
@ -234,10 +405,54 @@
|
||||
productReference = 331C8081294A63A400263BE5 /* RunnerTests.xctest */;
|
||||
productType = "com.apple.product-type.bundle.unit-test";
|
||||
};
|
||||
738C1EAA2D0D76A400A215F3 /* SolarWidgetExtension */ = {
|
||||
isa = PBXNativeTarget;
|
||||
buildConfigurationList = 738C1EBA2D0D76A500A215F3 /* Build configuration list for PBXNativeTarget "SolarWidgetExtension" */;
|
||||
buildPhases = (
|
||||
F2FCDA0E1BD434BF4883AFFD /* [CP] Check Pods Manifest.lock */,
|
||||
738C1EA72D0D76A400A215F3 /* Sources */,
|
||||
738C1EA82D0D76A400A215F3 /* Frameworks */,
|
||||
738C1EA92D0D76A400A215F3 /* Resources */,
|
||||
738C1EBE2D0D76C500A215F3 /* Copy Bundle Version */,
|
||||
);
|
||||
buildRules = (
|
||||
);
|
||||
dependencies = (
|
||||
);
|
||||
fileSystemSynchronizedGroups = (
|
||||
738C1EAE2D0D76A400A215F3 /* SolarWidget */,
|
||||
);
|
||||
name = SolarWidgetExtension;
|
||||
productName = SolarWidgetExtension;
|
||||
productReference = 738C1EAB2D0D76A400A215F3 /* SolarWidgetExtension.appex */;
|
||||
productType = "com.apple.product-type.app-extension";
|
||||
};
|
||||
73B774632D0E869200A789CE /* SolarShare */ = {
|
||||
isa = PBXNativeTarget;
|
||||
buildConfigurationList = 73B774732D0E869200A789CE /* Build configuration list for PBXNativeTarget "SolarShare" */;
|
||||
buildPhases = (
|
||||
9E6442609CE65E253572BC79 /* [CP] Check Pods Manifest.lock */,
|
||||
73B774602D0E869200A789CE /* Sources */,
|
||||
73B774612D0E869200A789CE /* Frameworks */,
|
||||
73B774622D0E869200A789CE /* Resources */,
|
||||
);
|
||||
buildRules = (
|
||||
);
|
||||
dependencies = (
|
||||
);
|
||||
fileSystemSynchronizedGroups = (
|
||||
73B774652D0E869200A789CE /* SolarShare */,
|
||||
);
|
||||
name = SolarShare;
|
||||
productName = SolarShare;
|
||||
productReference = 73B774642D0E869200A789CE /* SolarShare.appex */;
|
||||
productType = "com.apple.product-type.app-extension";
|
||||
};
|
||||
73DA89F92D05C7620024A03E /* SolarNotifyService */ = {
|
||||
isa = PBXNativeTarget;
|
||||
buildConfigurationList = 73DA8A072D05C7620024A03E /* Build configuration list for PBXNativeTarget "SolarNotifyService" */;
|
||||
buildPhases = (
|
||||
50F5704AB2E7309C916CA2E7 /* [CP] Check Pods Manifest.lock */,
|
||||
73DA89F62D05C7620024A03E /* Sources */,
|
||||
73DA89F72D05C7620024A03E /* Frameworks */,
|
||||
73DA89F82D05C7620024A03E /* Resources */,
|
||||
@ -250,8 +465,6 @@
|
||||
73DA89FB2D05C7620024A03E /* SolarNotifyService */,
|
||||
);
|
||||
name = SolarNotifyService;
|
||||
packageProductDependencies = (
|
||||
);
|
||||
productName = SolarNotifyService;
|
||||
productReference = 73DA89FA2D05C7620024A03E /* SolarNotifyService.appex */;
|
||||
productType = "com.apple.product-type.app-extension";
|
||||
@ -276,6 +489,12 @@
|
||||
);
|
||||
dependencies = (
|
||||
73DA8A002D05C7620024A03E /* PBXTargetDependency */,
|
||||
738C1EB72D0D76A500A215F3 /* PBXTargetDependency */,
|
||||
73B7746D2D0E869200A789CE /* PBXTargetDependency */,
|
||||
);
|
||||
fileSystemSynchronizedGroups = (
|
||||
738C1F4F2D0D91CC00A215F3 /* Data */,
|
||||
73BC736C2D0DDF5600956BE0 /* Service */,
|
||||
);
|
||||
name = Runner;
|
||||
productName = Runner;
|
||||
@ -289,7 +508,7 @@
|
||||
isa = PBXProject;
|
||||
attributes = {
|
||||
BuildIndependentTargetsInParallel = YES;
|
||||
LastSwiftUpdateCheck = 1610;
|
||||
LastSwiftUpdateCheck = 1620;
|
||||
LastUpgradeCheck = 1510;
|
||||
ORGANIZATIONNAME = "";
|
||||
TargetAttributes = {
|
||||
@ -297,6 +516,12 @@
|
||||
CreatedOnToolsVersion = 14.0;
|
||||
TestTargetID = 97C146ED1CF9000F007C117D;
|
||||
};
|
||||
738C1EAA2D0D76A400A215F3 = {
|
||||
CreatedOnToolsVersion = 16.2;
|
||||
};
|
||||
73B774632D0E869200A789CE = {
|
||||
CreatedOnToolsVersion = 16.2;
|
||||
};
|
||||
73DA89F92D05C7620024A03E = {
|
||||
CreatedOnToolsVersion = 16.1;
|
||||
};
|
||||
@ -307,7 +532,6 @@
|
||||
};
|
||||
};
|
||||
buildConfigurationList = 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */;
|
||||
compatibilityVersion = "Xcode 9.3";
|
||||
developmentRegion = en;
|
||||
hasScannedForEncodings = 0;
|
||||
knownRegions = (
|
||||
@ -315,6 +539,7 @@
|
||||
Base,
|
||||
);
|
||||
mainGroup = 97C146E51CF9000F007C117D;
|
||||
preferredProjectObjectVersion = 77;
|
||||
productRefGroup = 97C146EF1CF9000F007C117D /* Products */;
|
||||
projectDirPath = "";
|
||||
projectRoot = "";
|
||||
@ -322,6 +547,8 @@
|
||||
97C146ED1CF9000F007C117D /* Runner */,
|
||||
331C8080294A63A400263BE5 /* RunnerTests */,
|
||||
73DA89F92D05C7620024A03E /* SolarNotifyService */,
|
||||
738C1EAA2D0D76A400A215F3 /* SolarWidgetExtension */,
|
||||
73B774632D0E869200A789CE /* SolarShare */,
|
||||
);
|
||||
};
|
||||
/* End PBXProject section */
|
||||
@ -334,6 +561,20 @@
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
738C1EA92D0D76A400A215F3 /* Resources */ = {
|
||||
isa = PBXResourcesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
73B774622D0E869200A789CE /* Resources */ = {
|
||||
isa = PBXResourcesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
73DA89F82D05C7620024A03E /* Resources */ = {
|
||||
isa = PBXResourcesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
@ -388,7 +629,7 @@
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
shellPath = /bin/sh;
|
||||
shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" embed_and_thin";
|
||||
shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" embed_and_thin\n";
|
||||
};
|
||||
43B5CF57FD79BC21654EE037 /* [CP] Copy Pods Resources */ = {
|
||||
isa = PBXShellScriptBuildPhase;
|
||||
@ -407,6 +648,46 @@
|
||||
shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-resources.sh\"\n";
|
||||
showEnvVarsInLog = 0;
|
||||
};
|
||||
50F5704AB2E7309C916CA2E7 /* [CP] Check Pods Manifest.lock */ = {
|
||||
isa = PBXShellScriptBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
);
|
||||
inputFileListPaths = (
|
||||
);
|
||||
inputPaths = (
|
||||
"${PODS_PODFILE_DIR_PATH}/Podfile.lock",
|
||||
"${PODS_ROOT}/Manifest.lock",
|
||||
);
|
||||
name = "[CP] Check Pods Manifest.lock";
|
||||
outputFileListPaths = (
|
||||
);
|
||||
outputPaths = (
|
||||
"$(DERIVED_FILE_DIR)/Pods-SolarNotifyService-checkManifestLockResult.txt",
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
shellPath = /bin/sh;
|
||||
shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n";
|
||||
showEnvVarsInLog = 0;
|
||||
};
|
||||
738C1EBE2D0D76C500A215F3 /* Copy Bundle Version */ = {
|
||||
isa = PBXShellScriptBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
);
|
||||
inputFileListPaths = (
|
||||
);
|
||||
inputPaths = (
|
||||
);
|
||||
name = "Copy Bundle Version";
|
||||
outputFileListPaths = (
|
||||
);
|
||||
outputPaths = (
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
shellPath = /bin/sh;
|
||||
shellScript = "generatedPath=\"$SRCROOT/Flutter/Generated.xcconfig\"\n\n# Read and trim versionNumber and buildNumber\nversionNumber=$(grep FLUTTER_BUILD_NAME \"$generatedPath\" | cut -d '=' -f2 | xargs)\nbuildNumber=$(grep FLUTTER_BUILD_NUMBER \"$generatedPath\" | cut -d '=' -f2 | xargs)\n\ninfoPlistPath=\"$SRCROOT/HomeExampleWidget/Info.plist\"\n\n# Check and add CFBundleVersion if it does not exist\n/usr/libexec/PlistBuddy -c \"Print :CFBundleVersion\" \"$infoPlistPath\" 2>/dev/null\nif [ $? != 0 ]; then\n /usr/libexec/PlistBuddy -c \"Add :CFBundleVersion string $buildNumber\" \"$infoPlistPath\"\nelse\n /usr/libexec/PlistBuddy -c \"Set :CFBundleVersion $buildNumber\" \"$infoPlistPath\"\nfi\n\n# Check and add CFBundleShortVersionString if it does not exist\n/usr/libexec/PlistBuddy -c \"Print :CFBundleShortVersionString\" \"$infoPlistPath\" 2>/dev/null\nif [ $? != 0 ]; then\n /usr/libexec/PlistBuddy -c \"Add :CFBundleShortVersionString string $versionNumber\" \"$infoPlistPath\"\nelse\n /usr/libexec/PlistBuddy -c \"Set :CFBundleShortVersionString $versionNumber\" \"$infoPlistPath\"\nfi\n\n";
|
||||
};
|
||||
9740EEB61CF901F6004384FC /* Run Script */ = {
|
||||
isa = PBXShellScriptBuildPhase;
|
||||
alwaysOutOfDate = 1;
|
||||
@ -422,6 +703,28 @@
|
||||
shellPath = /bin/sh;
|
||||
shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" build";
|
||||
};
|
||||
9E6442609CE65E253572BC79 /* [CP] Check Pods Manifest.lock */ = {
|
||||
isa = PBXShellScriptBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
);
|
||||
inputFileListPaths = (
|
||||
);
|
||||
inputPaths = (
|
||||
"${PODS_PODFILE_DIR_PATH}/Podfile.lock",
|
||||
"${PODS_ROOT}/Manifest.lock",
|
||||
);
|
||||
name = "[CP] Check Pods Manifest.lock";
|
||||
outputFileListPaths = (
|
||||
);
|
||||
outputPaths = (
|
||||
"$(DERIVED_FILE_DIR)/Pods-SolarShare-checkManifestLockResult.txt",
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
shellPath = /bin/sh;
|
||||
shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n";
|
||||
showEnvVarsInLog = 0;
|
||||
};
|
||||
C431F2F1BD10FD03D14DDAE1 /* [CP] Check Pods Manifest.lock */ = {
|
||||
isa = PBXShellScriptBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
@ -466,6 +769,28 @@
|
||||
shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n";
|
||||
showEnvVarsInLog = 0;
|
||||
};
|
||||
F2FCDA0E1BD434BF4883AFFD /* [CP] Check Pods Manifest.lock */ = {
|
||||
isa = PBXShellScriptBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
);
|
||||
inputFileListPaths = (
|
||||
);
|
||||
inputPaths = (
|
||||
"${PODS_PODFILE_DIR_PATH}/Podfile.lock",
|
||||
"${PODS_ROOT}/Manifest.lock",
|
||||
);
|
||||
name = "[CP] Check Pods Manifest.lock";
|
||||
outputFileListPaths = (
|
||||
);
|
||||
outputPaths = (
|
||||
"$(DERIVED_FILE_DIR)/Pods-SolarWidgetExtension-checkManifestLockResult.txt",
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
shellPath = /bin/sh;
|
||||
shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n";
|
||||
showEnvVarsInLog = 0;
|
||||
};
|
||||
FC4815D44D909666EB1FA614 /* [CP] Embed Pods Frameworks */ = {
|
||||
isa = PBXShellScriptBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
@ -494,6 +819,20 @@
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
738C1EA72D0D76A400A215F3 /* Sources */ = {
|
||||
isa = PBXSourcesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
73B774602D0E869200A789CE /* Sources */ = {
|
||||
isa = PBXSourcesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
73DA89F62D05C7620024A03E /* Sources */ = {
|
||||
isa = PBXSourcesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
@ -507,6 +846,7 @@
|
||||
files = (
|
||||
74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */,
|
||||
1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */,
|
||||
7396A3522D16BD890095F4A8 /* NotifyDelegate.swift in Sources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
@ -518,6 +858,16 @@
|
||||
target = 97C146ED1CF9000F007C117D /* Runner */;
|
||||
targetProxy = 331C8085294A63A400263BE5 /* PBXContainerItemProxy */;
|
||||
};
|
||||
738C1EB72D0D76A500A215F3 /* PBXTargetDependency */ = {
|
||||
isa = PBXTargetDependency;
|
||||
target = 738C1EAA2D0D76A400A215F3 /* SolarWidgetExtension */;
|
||||
targetProxy = 738C1EB62D0D76A500A215F3 /* PBXContainerItemProxy */;
|
||||
};
|
||||
73B7746D2D0E869200A789CE /* PBXTargetDependency */ = {
|
||||
isa = PBXTargetDependency;
|
||||
target = 73B774632D0E869200A789CE /* SolarShare */;
|
||||
targetProxy = 73B7746C2D0E869200A789CE /* PBXContainerItemProxy */;
|
||||
};
|
||||
73DA8A002D05C7620024A03E /* PBXTargetDependency */ = {
|
||||
isa = PBXTargetDependency;
|
||||
target = 73DA89F92D05C7620024A03E /* SolarNotifyService */;
|
||||
@ -605,11 +955,13 @@
|
||||
CLANG_ENABLE_MODULES = YES;
|
||||
CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements;
|
||||
CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
|
||||
CUSTOM_GROUP_ID = group.solsynth.solian;
|
||||
DEVELOPMENT_TEAM = W7HPZ53V6B;
|
||||
ENABLE_BITCODE = NO;
|
||||
INFOPLIST_FILE = Runner/Info.plist;
|
||||
INFOPLIST_KEY_CFBundleDisplayName = Solian;
|
||||
INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.social-networking";
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 13.0;
|
||||
LD_RUNPATH_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
"@executable_path/Frameworks",
|
||||
@ -629,6 +981,7 @@
|
||||
BUNDLE_LOADER = "$(TEST_HOST)";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 1;
|
||||
DEVELOPMENT_TEAM = W7HPZ53V6B;
|
||||
GENERATE_INFOPLIST_FILE = YES;
|
||||
MARKETING_VERSION = 1.0;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = dev.solsynth.solian.RunnerTests;
|
||||
@ -647,6 +1000,7 @@
|
||||
BUNDLE_LOADER = "$(TEST_HOST)";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 1;
|
||||
DEVELOPMENT_TEAM = W7HPZ53V6B;
|
||||
GENERATE_INFOPLIST_FILE = YES;
|
||||
MARKETING_VERSION = 1.0;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = dev.solsynth.solian.RunnerTests;
|
||||
@ -663,6 +1017,7 @@
|
||||
BUNDLE_LOADER = "$(TEST_HOST)";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 1;
|
||||
DEVELOPMENT_TEAM = W7HPZ53V6B;
|
||||
GENERATE_INFOPLIST_FILE = YES;
|
||||
MARKETING_VERSION = 1.0;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = dev.solsynth.solian.RunnerTests;
|
||||
@ -672,8 +1027,135 @@
|
||||
};
|
||||
name = Profile;
|
||||
};
|
||||
73DA8A032D05C7620024A03E /* Debug */ = {
|
||||
738C1EBB2D0D76A500A215F3 /* Debug */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
baseConfigurationReference = 2134F3903A0E8EB8CC2670BE /* Pods-SolarWidgetExtension.debug.xcconfig */;
|
||||
buildSettings = {
|
||||
ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;
|
||||
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
|
||||
ASSETCATALOG_COMPILER_WIDGET_BACKGROUND_COLOR_NAME = WidgetBackground;
|
||||
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
|
||||
CLANG_CXX_LANGUAGE_STANDARD = "gnu++20";
|
||||
CLANG_ENABLE_OBJC_WEAK = YES;
|
||||
CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
|
||||
CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
|
||||
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
|
||||
CODE_SIGN_ENTITLEMENTS = SolarWidgetExtension.entitlements;
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 1;
|
||||
DEVELOPMENT_TEAM = W7HPZ53V6B;
|
||||
ENABLE_USER_SCRIPT_SANDBOXING = YES;
|
||||
GCC_C_LANGUAGE_STANDARD = gnu17;
|
||||
GENERATE_INFOPLIST_FILE = YES;
|
||||
INFOPLIST_FILE = SolarWidget/Info.plist;
|
||||
INFOPLIST_KEY_CFBundleDisplayName = SolarWidget;
|
||||
INFOPLIST_KEY_NSHumanReadableCopyright = "";
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 17.6;
|
||||
LD_RUNPATH_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
"@executable_path/Frameworks",
|
||||
"@executable_path/../../Frameworks",
|
||||
);
|
||||
LOCALIZATION_PREFERS_STRING_CATALOGS = YES;
|
||||
MARKETING_VERSION = 1.0;
|
||||
MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
|
||||
MTL_FAST_MATH = YES;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = dev.solsynth.solian.SolarWidget;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SKIP_INSTALL = YES;
|
||||
SWIFT_ACTIVE_COMPILATION_CONDITIONS = "DEBUG $(inherited)";
|
||||
SWIFT_EMIT_LOC_STRINGS = YES;
|
||||
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
|
||||
SWIFT_VERSION = 5.0;
|
||||
TARGETED_DEVICE_FAMILY = "1,2";
|
||||
};
|
||||
name = Debug;
|
||||
};
|
||||
738C1EBC2D0D76A500A215F3 /* Release */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
baseConfigurationReference = 6618E2E3015264643175B43D /* Pods-SolarWidgetExtension.release.xcconfig */;
|
||||
buildSettings = {
|
||||
ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;
|
||||
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
|
||||
ASSETCATALOG_COMPILER_WIDGET_BACKGROUND_COLOR_NAME = WidgetBackground;
|
||||
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
|
||||
CLANG_CXX_LANGUAGE_STANDARD = "gnu++20";
|
||||
CLANG_ENABLE_OBJC_WEAK = YES;
|
||||
CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
|
||||
CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
|
||||
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
|
||||
CODE_SIGN_ENTITLEMENTS = SolarWidgetExtension.entitlements;
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 1;
|
||||
DEVELOPMENT_TEAM = W7HPZ53V6B;
|
||||
ENABLE_USER_SCRIPT_SANDBOXING = YES;
|
||||
GCC_C_LANGUAGE_STANDARD = gnu17;
|
||||
GENERATE_INFOPLIST_FILE = YES;
|
||||
INFOPLIST_FILE = SolarWidget/Info.plist;
|
||||
INFOPLIST_KEY_CFBundleDisplayName = SolarWidget;
|
||||
INFOPLIST_KEY_NSHumanReadableCopyright = "";
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 17.6;
|
||||
LD_RUNPATH_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
"@executable_path/Frameworks",
|
||||
"@executable_path/../../Frameworks",
|
||||
);
|
||||
LOCALIZATION_PREFERS_STRING_CATALOGS = YES;
|
||||
MARKETING_VERSION = 1.0;
|
||||
MTL_FAST_MATH = YES;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = dev.solsynth.solian.SolarWidget;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SKIP_INSTALL = YES;
|
||||
SWIFT_EMIT_LOC_STRINGS = YES;
|
||||
SWIFT_VERSION = 5.0;
|
||||
TARGETED_DEVICE_FAMILY = "1,2";
|
||||
};
|
||||
name = Release;
|
||||
};
|
||||
738C1EBD2D0D76A500A215F3 /* Profile */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
baseConfigurationReference = BCE0C4086B776A27B202B373 /* Pods-SolarWidgetExtension.profile.xcconfig */;
|
||||
buildSettings = {
|
||||
ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;
|
||||
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
|
||||
ASSETCATALOG_COMPILER_WIDGET_BACKGROUND_COLOR_NAME = WidgetBackground;
|
||||
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
|
||||
CLANG_CXX_LANGUAGE_STANDARD = "gnu++20";
|
||||
CLANG_ENABLE_OBJC_WEAK = YES;
|
||||
CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
|
||||
CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
|
||||
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
|
||||
CODE_SIGN_ENTITLEMENTS = SolarWidgetExtension.entitlements;
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 1;
|
||||
DEVELOPMENT_TEAM = W7HPZ53V6B;
|
||||
ENABLE_USER_SCRIPT_SANDBOXING = YES;
|
||||
GCC_C_LANGUAGE_STANDARD = gnu17;
|
||||
GENERATE_INFOPLIST_FILE = YES;
|
||||
INFOPLIST_FILE = SolarWidget/Info.plist;
|
||||
INFOPLIST_KEY_CFBundleDisplayName = SolarWidget;
|
||||
INFOPLIST_KEY_NSHumanReadableCopyright = "";
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 17.6;
|
||||
LD_RUNPATH_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
"@executable_path/Frameworks",
|
||||
"@executable_path/../../Frameworks",
|
||||
);
|
||||
LOCALIZATION_PREFERS_STRING_CATALOGS = YES;
|
||||
MARKETING_VERSION = 1.0;
|
||||
MTL_FAST_MATH = YES;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = dev.solsynth.solian.SolarWidget;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SKIP_INSTALL = YES;
|
||||
SWIFT_EMIT_LOC_STRINGS = YES;
|
||||
SWIFT_VERSION = 5.0;
|
||||
TARGETED_DEVICE_FAMILY = "1,2";
|
||||
};
|
||||
name = Profile;
|
||||
};
|
||||
73B7746F2D0E869200A789CE /* Debug */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
baseConfigurationReference = 5922A50B1231B06B92E31F20 /* Pods-SolarShare.debug.xcconfig */;
|
||||
buildSettings = {
|
||||
ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;
|
||||
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
|
||||
@ -682,6 +1164,130 @@
|
||||
CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
|
||||
CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
|
||||
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
|
||||
CODE_SIGN_ENTITLEMENTS = SolarShare/SolarShare.entitlements;
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 1;
|
||||
CUSTOM_GROUP_ID = group.solsynth.solian;
|
||||
DEVELOPMENT_TEAM = W7HPZ53V6B;
|
||||
ENABLE_USER_SCRIPT_SANDBOXING = YES;
|
||||
GCC_C_LANGUAGE_STANDARD = gnu17;
|
||||
GENERATE_INFOPLIST_FILE = YES;
|
||||
INFOPLIST_FILE = SolarShare/Info.plist;
|
||||
INFOPLIST_KEY_CFBundleDisplayName = SolarShare;
|
||||
INFOPLIST_KEY_NSHumanReadableCopyright = "";
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 15.6;
|
||||
LD_RUNPATH_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
"@executable_path/Frameworks",
|
||||
"@executable_path/../../Frameworks",
|
||||
);
|
||||
LOCALIZATION_PREFERS_STRING_CATALOGS = YES;
|
||||
MARKETING_VERSION = 1.0;
|
||||
MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
|
||||
MTL_FAST_MATH = YES;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = dev.solsynth.solian.SolarShare;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SKIP_INSTALL = YES;
|
||||
SWIFT_ACTIVE_COMPILATION_CONDITIONS = "DEBUG $(inherited)";
|
||||
SWIFT_EMIT_LOC_STRINGS = YES;
|
||||
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
|
||||
SWIFT_VERSION = 5.0;
|
||||
TARGETED_DEVICE_FAMILY = "1,2";
|
||||
};
|
||||
name = Debug;
|
||||
};
|
||||
73B774702D0E869200A789CE /* Release */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
baseConfigurationReference = B1763F1D7318A2745CA7EDFE /* Pods-SolarShare.release.xcconfig */;
|
||||
buildSettings = {
|
||||
ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;
|
||||
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
|
||||
CLANG_CXX_LANGUAGE_STANDARD = "gnu++20";
|
||||
CLANG_ENABLE_OBJC_WEAK = YES;
|
||||
CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
|
||||
CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
|
||||
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
|
||||
CODE_SIGN_ENTITLEMENTS = SolarShare/SolarShare.entitlements;
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 1;
|
||||
CUSTOM_GROUP_ID = group.solsynth.solian;
|
||||
DEVELOPMENT_TEAM = W7HPZ53V6B;
|
||||
ENABLE_USER_SCRIPT_SANDBOXING = YES;
|
||||
GCC_C_LANGUAGE_STANDARD = gnu17;
|
||||
GENERATE_INFOPLIST_FILE = YES;
|
||||
INFOPLIST_FILE = SolarShare/Info.plist;
|
||||
INFOPLIST_KEY_CFBundleDisplayName = SolarShare;
|
||||
INFOPLIST_KEY_NSHumanReadableCopyright = "";
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 15.6;
|
||||
LD_RUNPATH_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
"@executable_path/Frameworks",
|
||||
"@executable_path/../../Frameworks",
|
||||
);
|
||||
LOCALIZATION_PREFERS_STRING_CATALOGS = YES;
|
||||
MARKETING_VERSION = 1.0;
|
||||
MTL_FAST_MATH = YES;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = dev.solsynth.solian.SolarShare;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SKIP_INSTALL = YES;
|
||||
SWIFT_EMIT_LOC_STRINGS = YES;
|
||||
SWIFT_VERSION = 5.0;
|
||||
TARGETED_DEVICE_FAMILY = "1,2";
|
||||
};
|
||||
name = Release;
|
||||
};
|
||||
73B774712D0E869200A789CE /* Profile */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
baseConfigurationReference = 2DA1B873D39B9FD33298BBCE /* Pods-SolarShare.profile.xcconfig */;
|
||||
buildSettings = {
|
||||
ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;
|
||||
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
|
||||
CLANG_CXX_LANGUAGE_STANDARD = "gnu++20";
|
||||
CLANG_ENABLE_OBJC_WEAK = YES;
|
||||
CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
|
||||
CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
|
||||
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
|
||||
CODE_SIGN_ENTITLEMENTS = SolarShare/SolarShare.entitlements;
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 1;
|
||||
CUSTOM_GROUP_ID = group.solsynth.solian;
|
||||
DEVELOPMENT_TEAM = W7HPZ53V6B;
|
||||
ENABLE_USER_SCRIPT_SANDBOXING = YES;
|
||||
GCC_C_LANGUAGE_STANDARD = gnu17;
|
||||
GENERATE_INFOPLIST_FILE = YES;
|
||||
INFOPLIST_FILE = SolarShare/Info.plist;
|
||||
INFOPLIST_KEY_CFBundleDisplayName = SolarShare;
|
||||
INFOPLIST_KEY_NSHumanReadableCopyright = "";
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 15.6;
|
||||
LD_RUNPATH_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
"@executable_path/Frameworks",
|
||||
"@executable_path/../../Frameworks",
|
||||
);
|
||||
LOCALIZATION_PREFERS_STRING_CATALOGS = YES;
|
||||
MARKETING_VERSION = 1.0;
|
||||
MTL_FAST_MATH = YES;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = dev.solsynth.solian.SolarShare;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SKIP_INSTALL = YES;
|
||||
SWIFT_EMIT_LOC_STRINGS = YES;
|
||||
SWIFT_VERSION = 5.0;
|
||||
TARGETED_DEVICE_FAMILY = "1,2";
|
||||
};
|
||||
name = Profile;
|
||||
};
|
||||
73DA8A032D05C7620024A03E /* Debug */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
baseConfigurationReference = D96D1DB4ED46A2640C1B9D34 /* Pods-SolarNotifyService.debug.xcconfig */;
|
||||
buildSettings = {
|
||||
ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;
|
||||
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
|
||||
CLANG_CXX_LANGUAGE_STANDARD = "gnu++20";
|
||||
CLANG_ENABLE_OBJC_WEAK = YES;
|
||||
CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
|
||||
CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
|
||||
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
|
||||
CODE_SIGN_ENTITLEMENTS = SolarNotifyService/SolarNotifyService.entitlements;
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 1;
|
||||
DEVELOPMENT_TEAM = W7HPZ53V6B;
|
||||
@ -691,7 +1297,7 @@
|
||||
INFOPLIST_FILE = SolarNotifyService/Info.plist;
|
||||
INFOPLIST_KEY_CFBundleDisplayName = SolarNotifyService;
|
||||
INFOPLIST_KEY_NSHumanReadableCopyright = "";
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 18.1;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 15.6;
|
||||
LD_RUNPATH_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
"@executable_path/Frameworks",
|
||||
@ -714,6 +1320,7 @@
|
||||
};
|
||||
73DA8A042D05C7620024A03E /* Release */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
baseConfigurationReference = D7E1FA77FDA53439DB2C0E75 /* Pods-SolarNotifyService.release.xcconfig */;
|
||||
buildSettings = {
|
||||
ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;
|
||||
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
|
||||
@ -722,6 +1329,7 @@
|
||||
CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
|
||||
CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
|
||||
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
|
||||
CODE_SIGN_ENTITLEMENTS = SolarNotifyService/SolarNotifyService.entitlements;
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 1;
|
||||
DEVELOPMENT_TEAM = W7HPZ53V6B;
|
||||
@ -731,7 +1339,7 @@
|
||||
INFOPLIST_FILE = SolarNotifyService/Info.plist;
|
||||
INFOPLIST_KEY_CFBundleDisplayName = SolarNotifyService;
|
||||
INFOPLIST_KEY_NSHumanReadableCopyright = "";
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 18.1;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 15.6;
|
||||
LD_RUNPATH_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
"@executable_path/Frameworks",
|
||||
@ -751,6 +1359,7 @@
|
||||
};
|
||||
73DA8A052D05C7620024A03E /* Profile */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
baseConfigurationReference = 4CBF45ABD292EE527D0A4D1E /* Pods-SolarNotifyService.profile.xcconfig */;
|
||||
buildSettings = {
|
||||
ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;
|
||||
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
|
||||
@ -759,6 +1368,7 @@
|
||||
CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
|
||||
CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
|
||||
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
|
||||
CODE_SIGN_ENTITLEMENTS = SolarNotifyService/SolarNotifyService.entitlements;
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 1;
|
||||
DEVELOPMENT_TEAM = W7HPZ53V6B;
|
||||
@ -768,7 +1378,7 @@
|
||||
INFOPLIST_FILE = SolarNotifyService/Info.plist;
|
||||
INFOPLIST_KEY_CFBundleDisplayName = SolarNotifyService;
|
||||
INFOPLIST_KEY_NSHumanReadableCopyright = "";
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 18.1;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 15.6;
|
||||
LD_RUNPATH_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
"@executable_path/Frameworks",
|
||||
@ -905,11 +1515,13 @@
|
||||
CLANG_ENABLE_MODULES = YES;
|
||||
CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements;
|
||||
CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
|
||||
CUSTOM_GROUP_ID = group.solsynth.solian;
|
||||
DEVELOPMENT_TEAM = W7HPZ53V6B;
|
||||
ENABLE_BITCODE = NO;
|
||||
INFOPLIST_FILE = Runner/Info.plist;
|
||||
INFOPLIST_KEY_CFBundleDisplayName = Solian;
|
||||
INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.social-networking";
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 13.0;
|
||||
LD_RUNPATH_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
"@executable_path/Frameworks",
|
||||
@ -931,11 +1543,13 @@
|
||||
CLANG_ENABLE_MODULES = YES;
|
||||
CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements;
|
||||
CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
|
||||
CUSTOM_GROUP_ID = group.solsynth.solian;
|
||||
DEVELOPMENT_TEAM = W7HPZ53V6B;
|
||||
ENABLE_BITCODE = NO;
|
||||
INFOPLIST_FILE = Runner/Info.plist;
|
||||
INFOPLIST_KEY_CFBundleDisplayName = Solian;
|
||||
INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.social-networking";
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 13.0;
|
||||
LD_RUNPATH_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
"@executable_path/Frameworks",
|
||||
@ -961,6 +1575,26 @@
|
||||
defaultConfigurationIsVisible = 0;
|
||||
defaultConfigurationName = Release;
|
||||
};
|
||||
738C1EBA2D0D76A500A215F3 /* Build configuration list for PBXNativeTarget "SolarWidgetExtension" */ = {
|
||||
isa = XCConfigurationList;
|
||||
buildConfigurations = (
|
||||
738C1EBB2D0D76A500A215F3 /* Debug */,
|
||||
738C1EBC2D0D76A500A215F3 /* Release */,
|
||||
738C1EBD2D0D76A500A215F3 /* Profile */,
|
||||
);
|
||||
defaultConfigurationIsVisible = 0;
|
||||
defaultConfigurationName = Release;
|
||||
};
|
||||
73B774732D0E869200A789CE /* Build configuration list for PBXNativeTarget "SolarShare" */ = {
|
||||
isa = XCConfigurationList;
|
||||
buildConfigurations = (
|
||||
73B7746F2D0E869200A789CE /* Debug */,
|
||||
73B774702D0E869200A789CE /* Release */,
|
||||
73B774712D0E869200A789CE /* Profile */,
|
||||
);
|
||||
defaultConfigurationIsVisible = 0;
|
||||
defaultConfigurationName = Release;
|
||||
};
|
||||
73DA8A072D05C7620024A03E /* Build configuration list for PBXNativeTarget "SolarNotifyService" */ = {
|
||||
isa = XCConfigurationList;
|
||||
buildConfigurations = (
|
||||
|
@ -1,13 +1,26 @@
|
||||
import Flutter
|
||||
import UIKit
|
||||
|
||||
import workmanager
|
||||
|
||||
@main
|
||||
@objc class AppDelegate: FlutterAppDelegate {
|
||||
override func application(
|
||||
_ application: UIApplication,
|
||||
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
|
||||
) -> Bool {
|
||||
GeneratedPluginRegistrant.register(with: self)
|
||||
return super.application(application, didFinishLaunchingWithOptions: launchOptions)
|
||||
}
|
||||
let notifyDelegate = NotifyDelegate()
|
||||
|
||||
override func application(
|
||||
_ application: UIApplication,
|
||||
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
|
||||
) -> Bool {
|
||||
GeneratedPluginRegistrant.register(with: self)
|
||||
WorkmanagerPlugin.setPluginRegistrantCallback { registry in
|
||||
GeneratedPluginRegistrant.register(with: registry)
|
||||
}
|
||||
|
||||
UIApplication.shared.setMinimumBackgroundFetchInterval(TimeInterval(60*5))
|
||||
|
||||
UNUserNotificationCenter.current().delegate = notifyDelegate
|
||||
|
||||
return super.application(application, didFinishLaunchingWithOptions: launchOptions)
|
||||
}
|
||||
|
||||
}
|
||||
|
39
ios/Runner/AppIntent.swift
Normal file
39
ios/Runner/AppIntent.swift
Normal file
@ -0,0 +1,39 @@
|
||||
//
|
||||
// AppIntent.swift
|
||||
// Runner
|
||||
//
|
||||
// Created by LittleSheep on 2024/12/21.
|
||||
//
|
||||
|
||||
import AppIntents
|
||||
import Flutter
|
||||
import Foundation
|
||||
import home_widget
|
||||
|
||||
@available(iOS 17, *)
|
||||
public struct AppBackgroundIntent: AppIntent {
|
||||
static public var title: LocalizedStringResource = "Solar Network Background Intent"
|
||||
|
||||
@Parameter(title: "Widget URI")
|
||||
var url: URL?
|
||||
|
||||
@Parameter(title: "AppGroup")
|
||||
var appGroup: String?
|
||||
|
||||
public init() {}
|
||||
|
||||
public init(url: URL?, appGroup: String?) {
|
||||
self.url = url
|
||||
self.appGroup = appGroup
|
||||
}
|
||||
|
||||
public func perform() async throws -> some IntentResult {
|
||||
await HomeWidgetBackgroundWorker.run(url: url, appGroup: appGroup!)
|
||||
|
||||
return .result()
|
||||
}
|
||||
}
|
||||
|
||||
@available(iOS 17, *)
|
||||
@available(iOSApplicationExtension, unavailable)
|
||||
extension AppBackgroundIntent: ForegroundContinuableIntent {}
|
38
ios/Runner/Data/Post.swift
Normal file
38
ios/Runner/Data/Post.swift
Normal file
@ -0,0 +1,38 @@
|
||||
//
|
||||
// SolarPost.swift
|
||||
// Runner
|
||||
//
|
||||
// Created by LittleSheep on 2024/12/14.
|
||||
//
|
||||
|
||||
|
||||
import Foundation
|
||||
|
||||
struct SolarPost : Codable {
|
||||
let id: Int
|
||||
let body: SolarPostBody
|
||||
let publisher: SolarPublisher
|
||||
let publisherId: Int
|
||||
let createdAt: Date
|
||||
let updatedAt: Date
|
||||
let editedAt: Date?
|
||||
let publishedAt: Date?
|
||||
}
|
||||
|
||||
struct SolarPostBody : Codable {
|
||||
let content: String?
|
||||
let title: String?
|
||||
let description: String?
|
||||
let attachments: [String]?
|
||||
}
|
||||
|
||||
struct SolarPublisher : Codable {
|
||||
let id: Int
|
||||
let name: String
|
||||
let nick: String
|
||||
let description: String?
|
||||
let avatar: String?
|
||||
let banner: String?
|
||||
let createdAt: Date
|
||||
let updatedAt: Date
|
||||
}
|
21
ios/Runner/Data/User.swift
Normal file
21
ios/Runner/Data/User.swift
Normal file
@ -0,0 +1,21 @@
|
||||
//
|
||||
// SolarData.swift
|
||||
// Runner
|
||||
//
|
||||
// Created by LittleSheep on 2024/12/14.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
struct SolarUser: Codable {
|
||||
let id: Int
|
||||
let name: String
|
||||
let nick: String
|
||||
}
|
||||
|
||||
struct SolarCheckInRecord: Codable {
|
||||
let id: Int
|
||||
let resultTier: Int
|
||||
let resultExperience: Int
|
||||
let createdAt: Date
|
||||
}
|
@ -2,6 +2,8 @@
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>AppGroupId</key>
|
||||
<string>group.solsynth.solian</string>
|
||||
<key>CADisableMinimumFrameDurationOnPhone</key>
|
||||
<true/>
|
||||
<key>CFBundleDevelopmentRegion</key>
|
||||
@ -27,6 +29,17 @@
|
||||
<string>$(FLUTTER_BUILD_NAME)</string>
|
||||
<key>CFBundleSignature</key>
|
||||
<string>????</string>
|
||||
<key>CFBundleURLTypes</key>
|
||||
<array>
|
||||
<dict>
|
||||
<key>CFBundleTypeRole</key>
|
||||
<string>Editor</string>
|
||||
<key>CFBundleURLSchemes</key>
|
||||
<array>
|
||||
<string>ShareMedia-$(PRODUCT_BUNDLE_IDENTIFIER)</string>
|
||||
</array>
|
||||
</dict>
|
||||
</array>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>$(FLUTTER_BUILD_NUMBER)</string>
|
||||
<key>ITSAppUsesNonExemptEncryption</key>
|
||||
@ -34,9 +47,9 @@
|
||||
<key>LSRequiresIPhoneOS</key>
|
||||
<true/>
|
||||
<key>NSCameraUsageDescription</key>
|
||||
<string>Grant access to Photo Library will allow Solian take photo or video for your post.</string>
|
||||
<string>Grant access to Camera will allow Solian take photo or video for your post.</string>
|
||||
<key>NSMicrophoneUsageDescription</key>
|
||||
<string>Grant access to Photo Library will allow Solian record audio for your post.</string>
|
||||
<string>Grant access to Microphone will allow Solian record audio for your post.</string>
|
||||
<key>NSPhotoLibraryAddUsageDescription</key>
|
||||
<string>Grant access to Photo Library will allow Solian download photo to album for you.</string>
|
||||
<key>NSPhotoLibraryUsageDescription</key>
|
||||
|
55
ios/Runner/NotifyDelegate.swift
Normal file
55
ios/Runner/NotifyDelegate.swift
Normal file
@ -0,0 +1,55 @@
|
||||
//
|
||||
// NotifyDelegate.swift
|
||||
// Runner
|
||||
//
|
||||
// Created by LittleSheep on 2024/12/21.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import home_widget
|
||||
import Alamofire
|
||||
|
||||
class NotifyDelegate: UIResponder, UNUserNotificationCenterDelegate {
|
||||
func userNotificationCenter(_ center: UNUserNotificationCenter, didReceive response: UNNotificationResponse, withCompletionHandler completionHandler: @escaping () -> Void) {
|
||||
if let textResponse = response as? UNTextInputNotificationResponse {
|
||||
let content = response.notification.request.content
|
||||
guard let metadata = content.userInfo["metadata"] as? [AnyHashable: Any] else {
|
||||
return
|
||||
}
|
||||
|
||||
let channelId = metadata["channel_id"] as? Int
|
||||
let eventId = metadata["event_id"] as? Int
|
||||
|
||||
let replyToken = metadata["reply_token"] as? String
|
||||
|
||||
if (channelId == nil || eventId == nil || replyToken == nil) {
|
||||
return;
|
||||
}
|
||||
|
||||
let serverUrl = "https://api.sn.solsynth.dev"
|
||||
let url = "\(serverUrl)/cgi/im/quick/\(channelId!)/reply/\(eventId!)?replyToken=\(replyToken!)"
|
||||
|
||||
let parameters: [String: Any] = [
|
||||
"type": "messages.new",
|
||||
"body": [
|
||||
"text": textResponse.userText,
|
||||
"algorithm": "plain"
|
||||
]
|
||||
]
|
||||
|
||||
AF.request(url, method: .post, parameters: parameters, encoding: JSONEncoding.default)
|
||||
.validate()
|
||||
.responseString { response in
|
||||
switch response.result {
|
||||
case .success(_):
|
||||
break
|
||||
case .failure(let error):
|
||||
print("Failed to send chat reply message: \(error)")
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
completionHandler()
|
||||
}
|
||||
}
|
@ -4,7 +4,16 @@
|
||||
<dict>
|
||||
<key>aps-environment</key>
|
||||
<string>development</string>
|
||||
<key>com.apple.developer.associated-domains</key>
|
||||
<array>
|
||||
<string>webcredentials:sn.solsynth.dev</string>
|
||||
<string>applinks:sn.solsynth.dev</string>
|
||||
</array>
|
||||
<key>com.apple.developer.usernotifications.communication</key>
|
||||
<true/>
|
||||
<key>com.apple.security.application-groups</key>
|
||||
<array>
|
||||
<string>group.solsynth.solian</string>
|
||||
</array>
|
||||
</dict>
|
||||
</plist>
|
||||
|
14
ios/Runner/Service/Attachment.swift
Normal file
14
ios/Runner/Service/Attachment.swift
Normal file
@ -0,0 +1,14 @@
|
||||
//
|
||||
// Attachment.swift
|
||||
// Runner
|
||||
//
|
||||
// Created by LittleSheep on 2024/12/14.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
func getAttachmentUrl(for identifier: String) -> String {
|
||||
let serverBaseUrl = "https://api.sn.solsynth.dev"
|
||||
|
||||
return identifier.starts(with: "http") ? identifier : "\(serverBaseUrl)/cgi/uc/attachments/\(identifier)"
|
||||
}
|
@ -7,6 +7,8 @@
|
||||
|
||||
import UserNotifications
|
||||
import Intents
|
||||
import Kingfisher
|
||||
import UniformTypeIdentifiers
|
||||
|
||||
enum ParseNotificationPayloadError: Error {
|
||||
case missingMetadata(String)
|
||||
@ -17,63 +19,6 @@ class NotificationService: UNNotificationServiceExtension {
|
||||
|
||||
private var contentHandler: ((UNNotificationContent) -> Void)?
|
||||
private var bestAttemptContent: UNMutableNotificationContent?
|
||||
private let serverBaseUrl = "https://api.sn.solsynth.dev"
|
||||
|
||||
private func getAttachmentUrl(for identifier: String) -> String {
|
||||
identifier.starts(with: "http") ? identifier : "\(serverBaseUrl)/cgi/uc/attachments/\(identifier)"
|
||||
}
|
||||
|
||||
private func fetchAvatarImage(from url: String, completion: @escaping (INImage?) -> Void) {
|
||||
guard let imageURL = URL(string: url) else {
|
||||
completion(nil)
|
||||
return
|
||||
}
|
||||
|
||||
// Define a cache location based on the URL hash
|
||||
let cacheFileName = imageURL.lastPathComponent
|
||||
let tempDirectory = FileManager.default.temporaryDirectory
|
||||
let cachedFileUrl = tempDirectory.appendingPathComponent(cacheFileName)
|
||||
|
||||
// Check if the image is already cached
|
||||
if FileManager.default.fileExists(atPath: cachedFileUrl.path) {
|
||||
do {
|
||||
let data = try Data(contentsOf: cachedFileUrl)
|
||||
let cachedImage = INImage(imageData: data) // No optional binding here
|
||||
completion(cachedImage)
|
||||
return
|
||||
} catch {
|
||||
print("Failed to load cached avatar image: \(error.localizedDescription)")
|
||||
try? FileManager.default.removeItem(at: cachedFileUrl) // Clear corrupted cache
|
||||
}
|
||||
}
|
||||
|
||||
// Download the image if not cached
|
||||
let session = URLSession(configuration: .default)
|
||||
session.downloadTask(with: imageURL) { localUrl, response, error in
|
||||
if let error = error {
|
||||
print("Failed to fetch avatar image: \(error.localizedDescription)")
|
||||
completion(nil)
|
||||
return
|
||||
}
|
||||
|
||||
guard let localUrl = localUrl, let data = try? Data(contentsOf: localUrl) else {
|
||||
print("Failed to fetch data for avatar image.")
|
||||
completion(nil)
|
||||
return
|
||||
}
|
||||
|
||||
do {
|
||||
// Cache the downloaded file
|
||||
try FileManager.default.moveItem(at: localUrl, to: cachedFileUrl)
|
||||
} catch {
|
||||
print("Failed to cache avatar image: \(error.localizedDescription)")
|
||||
}
|
||||
|
||||
// Create INImage from the downloaded data
|
||||
let inImage = INImage(imageData: data) // Create directly
|
||||
completion(inImage)
|
||||
}.resume()
|
||||
}
|
||||
|
||||
override func didReceive(
|
||||
_ request: UNNotificationRequest,
|
||||
@ -117,16 +62,39 @@ class NotificationService: UNNotificationServiceExtension {
|
||||
throw ParseNotificationPayloadError.missingAvatarUrl("The notification has no avatar.")
|
||||
}
|
||||
|
||||
let replyableMessageCategory = UNNotificationCategory(
|
||||
identifier: content.categoryIdentifier,
|
||||
actions: [
|
||||
UNTextInputNotificationAction(
|
||||
identifier: "reply_action",
|
||||
title: "Reply",
|
||||
options: []
|
||||
),
|
||||
],
|
||||
intentIdentifiers: [],
|
||||
options: []
|
||||
)
|
||||
|
||||
UNUserNotificationCenter.current().setNotificationCategories([replyableMessageCategory])
|
||||
content.categoryIdentifier = replyableMessageCategory.identifier
|
||||
|
||||
let metadataCopy = metadata as? [String: String] ?? [:]
|
||||
let avatarUrl = getAttachmentUrl(for: avatarIdentifier)
|
||||
fetchAvatarImage(from: avatarUrl) { [weak self] inImage in
|
||||
guard let self = self else { return }
|
||||
KingfisherManager.shared.retrieveImage(with: URL(string: avatarUrl)!, completionHandler: { result in
|
||||
var image: Data?
|
||||
switch result {
|
||||
case .success(let value):
|
||||
image = value.image.pngData()
|
||||
case .failure(let error):
|
||||
print("Unable to get avatar url: \(error)")
|
||||
}
|
||||
|
||||
let handle = INPersonHandle(value: "\(metadata["user_id"] ?? "")", type: .unknown)
|
||||
let handle = INPersonHandle(value: "\(metadataCopy["user_id"] ?? "")", type: .unknown)
|
||||
let sender = INPerson(
|
||||
personHandle: handle,
|
||||
nameComponents: nil,
|
||||
displayName: content.title,
|
||||
image: inImage,
|
||||
image: image == nil ? nil : INImage(imageData: image!),
|
||||
contactIdentifier: nil,
|
||||
customIdentifier: nil
|
||||
)
|
||||
@ -137,12 +105,12 @@ class NotificationService: UNNotificationServiceExtension {
|
||||
let updatedContent = try? request.content.updating(from: intent)
|
||||
self.contentHandler?(updatedContent ?? content)
|
||||
} else {
|
||||
let intent = self.createMessageIntent(with: sender, metadata: metadata, body: content.body)
|
||||
let intent = self.createMessageIntent(with: sender, metadata: metadataCopy, body: content.body)
|
||||
self.donateInteraction(for: intent)
|
||||
let updatedContent = try? request.content.updating(from: intent)
|
||||
self.contentHandler?(updatedContent ?? content)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
private func handleDefaultNotification(content: UNMutableNotificationContent) throws {
|
||||
@ -151,15 +119,15 @@ class NotificationService: UNNotificationServiceExtension {
|
||||
}
|
||||
|
||||
if let imageIdentifier = metadata["image"] as? String {
|
||||
attachMedia(to: content, withIdentifier: imageIdentifier)
|
||||
attachMedia(to: content, withIdentifier: imageIdentifier, fileType: UTType.jpeg, doScaleDown: true)
|
||||
} else if let avatarIdentifier = metadata["avatar"] as? String {
|
||||
attachMedia(to: content, withIdentifier: avatarIdentifier)
|
||||
attachMedia(to: content, withIdentifier: avatarIdentifier, fileType: UTType.jpeg, doScaleDown: true)
|
||||
} else {
|
||||
contentHandler?(content)
|
||||
}
|
||||
|
||||
contentHandler?(content)
|
||||
}
|
||||
|
||||
private func attachMedia(to content: UNMutableNotificationContent, withIdentifier identifier: String) {
|
||||
private func attachMedia(to content: UNMutableNotificationContent, withIdentifier identifier: String, fileType type: UTType?, doScaleDown scaleDown: Bool = false) {
|
||||
let attachmentUrl = getAttachmentUrl(for: identifier)
|
||||
|
||||
guard let remoteUrl = URL(string: attachmentUrl) else {
|
||||
@ -167,49 +135,62 @@ class NotificationService: UNNotificationServiceExtension {
|
||||
return
|
||||
}
|
||||
|
||||
// Define a cache location based on the identifier
|
||||
let tempDirectory = FileManager.default.temporaryDirectory
|
||||
let cachedFileUrl = tempDirectory.appendingPathComponent(identifier)
|
||||
let targetSize = 800
|
||||
let scaleProcessor = ResizingImageProcessor(referenceSize: CGSize(width: targetSize, height: targetSize), mode: .aspectFit)
|
||||
|
||||
if FileManager.default.fileExists(atPath: cachedFileUrl.path) {
|
||||
// Use cached file
|
||||
attachLocalMedia(to: content, from: cachedFileUrl, withIdentifier: identifier)
|
||||
} else {
|
||||
// Download and cache the file
|
||||
let session = URLSession(configuration: .default)
|
||||
session.downloadTask(with: remoteUrl) { [weak content] localUrl, response, error in
|
||||
guard let content = content else { return }
|
||||
|
||||
if let error = error {
|
||||
print("Failed to download media: \(error.localizedDescription)")
|
||||
self.contentHandler?(content)
|
||||
return
|
||||
}
|
||||
|
||||
guard let localUrl = localUrl else {
|
||||
print("No local file URL after download")
|
||||
self.contentHandler?(content)
|
||||
return
|
||||
}
|
||||
KingfisherManager.shared.retrieveImage(with: remoteUrl, options: scaleDown ? [
|
||||
.processor(scaleProcessor)
|
||||
] : nil) { [weak self] result in
|
||||
guard let self = self else { return }
|
||||
|
||||
switch result {
|
||||
case .success(let retrievalResult):
|
||||
// The image is either retrieved from cache or downloaded
|
||||
let tempDirectory = FileManager.default.temporaryDirectory
|
||||
let cachedFileUrl = tempDirectory.appendingPathComponent(identifier)
|
||||
|
||||
do {
|
||||
// Move the downloaded file to the cache
|
||||
try FileManager.default.moveItem(at: localUrl, to: cachedFileUrl)
|
||||
self.attachLocalMedia(to: content, from: cachedFileUrl, withIdentifier: identifier)
|
||||
// Write the image data to a temporary file for UNNotificationAttachment
|
||||
try retrievalResult.image.pngData()?.write(to: cachedFileUrl)
|
||||
self.attachLocalMedia(to: content, fileType: type?.identifier, from: cachedFileUrl, withIdentifier: identifier)
|
||||
} catch {
|
||||
print("Failed to cache media file: \(error.localizedDescription)")
|
||||
print("Failed to write media to temporary file: \(error.localizedDescription)")
|
||||
self.contentHandler?(content)
|
||||
}
|
||||
}.resume()
|
||||
|
||||
case .failure(let error):
|
||||
print("Failed to retrieve image: \(error.localizedDescription)")
|
||||
self.contentHandler?(content)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private func attachLocalMedia(to content: UNMutableNotificationContent, from localUrl: URL, withIdentifier identifier: String) {
|
||||
if let attachment = try? UNNotificationAttachment(identifier: identifier, url: localUrl) {
|
||||
|
||||
private func attachLocalMedia(to content: UNMutableNotificationContent, fileType type: String?, from localUrl: URL, withIdentifier identifier: String) {
|
||||
do {
|
||||
let attachment = try UNNotificationAttachment(identifier: identifier, url: localUrl, options: [
|
||||
UNNotificationAttachmentOptionsTypeHintKey: type as Any,
|
||||
UNNotificationAttachmentOptionsThumbnailHiddenKey: 0,
|
||||
])
|
||||
content.attachments = [attachment]
|
||||
} else {
|
||||
print("Failed to create attachment from cached file: \(localUrl.path)")
|
||||
} catch let error as NSError {
|
||||
// Log detailed error information
|
||||
print("Failed to create attachment from file at \(localUrl.path)")
|
||||
print("Error: \(error.localizedDescription)")
|
||||
|
||||
// Check specific error codes if needed
|
||||
if error.domain == NSCocoaErrorDomain {
|
||||
switch error.code {
|
||||
case NSFileReadNoSuchFileError:
|
||||
print("File does not exist at \(localUrl.path)")
|
||||
case NSFileReadNoPermissionError:
|
||||
print("No permission to read file at \(localUrl.path)")
|
||||
default:
|
||||
print("Unhandled file error: \(error.code)")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Call content handler regardless of success or failure
|
||||
self.contentHandler?(content)
|
||||
}
|
||||
|
||||
|
10
ios/SolarNotifyService/SolarNotifyService.entitlements
Normal file
10
ios/SolarNotifyService/SolarNotifyService.entitlements
Normal file
@ -0,0 +1,10 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>com.apple.security.application-groups</key>
|
||||
<array>
|
||||
<string>group.solsynth.solian</string>
|
||||
</array>
|
||||
</dict>
|
||||
</plist>
|
24
ios/SolarShare/Base.lproj/MainInterface.storyboard
Normal file
24
ios/SolarShare/Base.lproj/MainInterface.storyboard
Normal file
@ -0,0 +1,24 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="13122.16" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES" initialViewController="j1y-V4-xli">
|
||||
<dependencies>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="13104.12"/>
|
||||
<capability name="Safe area layout guides" minToolsVersion="9.0"/>
|
||||
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
|
||||
</dependencies>
|
||||
<scenes>
|
||||
<!--Share View Controller-->
|
||||
<scene sceneID="ceB-am-kn3">
|
||||
<objects>
|
||||
<viewController id="j1y-V4-xli" customClass="ShareViewController" customModuleProvider="target" sceneMemberID="viewController">
|
||||
<view key="view" opaque="NO" contentMode="scaleToFill" id="wbc-yd-nQP">
|
||||
<rect key="frame" x="0.0" y="0.0" width="375" height="667"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
<color key="backgroundColor" red="0.0" green="0.0" blue="0.0" alpha="0.0" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
<viewLayoutGuide key="safeArea" id="1Xd-am-t49"/>
|
||||
</view>
|
||||
</viewController>
|
||||
<placeholder placeholderIdentifier="IBFirstResponder" id="CEy-Cv-SGf" userLabel="First Responder" sceneMemberID="firstResponder"/>
|
||||
</objects>
|
||||
</scene>
|
||||
</scenes>
|
||||
</document>
|
36
ios/SolarShare/Info.plist
Normal file
36
ios/SolarShare/Info.plist
Normal file
@ -0,0 +1,36 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>PHSupportedMediaTypes</key>
|
||||
<array>
|
||||
<string>Video</string>
|
||||
<string>Image</string>
|
||||
</array>
|
||||
<key>NSExtension</key>
|
||||
<dict>
|
||||
<key>NSExtensionAttributes</key>
|
||||
<dict>
|
||||
<key>NSExtensionActivationRule</key>
|
||||
<dict>
|
||||
<key>NSExtensionActivationSupportsText</key>
|
||||
<true/>
|
||||
<key>NSExtensionActivationSupportsWebURLWithMaxCount</key>
|
||||
<integer>15</integer>
|
||||
<key>NSExtensionActivationSupportsImageWithMaxCount</key>
|
||||
<integer>15</integer>
|
||||
<key>NSExtensionActivationSupportsMovieWithMaxCount</key>
|
||||
<integer>15</integer>
|
||||
<key>NSExtensionActivationSupportsFileWithMaxCount</key>
|
||||
<integer>15</integer>
|
||||
</dict>
|
||||
</dict>
|
||||
<key>NSExtensionMainStoryboard</key>
|
||||
<string>MainInterface</string>
|
||||
<key>NSExtensionPointIdentifier</key>
|
||||
<string>com.apple.share-services</string>
|
||||
</dict>
|
||||
<key>AppGroupId</key>
|
||||
<string>group.solsynth.solian</string>
|
||||
</dict>
|
||||
</plist>
|
18
ios/SolarShare/ShareViewController.swift
Normal file
18
ios/SolarShare/ShareViewController.swift
Normal file
@ -0,0 +1,18 @@
|
||||
//
|
||||
// ShareViewController.swift
|
||||
// SolarShare
|
||||
//
|
||||
// Created by LittleSheep on 2024/12/15.
|
||||
//
|
||||
|
||||
import receive_sharing_intent
|
||||
|
||||
class ShareViewController: RSIShareViewController {
|
||||
|
||||
// Use this method to return false if you don't want to redirect to host app automatically.
|
||||
// Default is true
|
||||
override func shouldAutoRedirect() -> Bool {
|
||||
return true
|
||||
}
|
||||
|
||||
}
|
10
ios/SolarShare/SolarShare.entitlements
Normal file
10
ios/SolarShare/SolarShare.entitlements
Normal file
@ -0,0 +1,10 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>com.apple.security.application-groups</key>
|
||||
<array>
|
||||
<string>group.solsynth.solian</string>
|
||||
</array>
|
||||
</dict>
|
||||
</plist>
|
@ -0,0 +1,11 @@
|
||||
{
|
||||
"colors" : [
|
||||
{
|
||||
"idiom" : "universal"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
@ -0,0 +1,35 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"platform" : "ios",
|
||||
"size" : "1024x1024"
|
||||
},
|
||||
{
|
||||
"appearances" : [
|
||||
{
|
||||
"appearance" : "luminosity",
|
||||
"value" : "dark"
|
||||
}
|
||||
],
|
||||
"idiom" : "universal",
|
||||
"platform" : "ios",
|
||||
"size" : "1024x1024"
|
||||
},
|
||||
{
|
||||
"appearances" : [
|
||||
{
|
||||
"appearance" : "luminosity",
|
||||
"value" : "tinted"
|
||||
}
|
||||
],
|
||||
"idiom" : "universal",
|
||||
"platform" : "ios",
|
||||
"size" : "1024x1024"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
6
ios/SolarWidget/Assets.xcassets/Contents.json
Normal file
6
ios/SolarWidget/Assets.xcassets/Contents.json
Normal file
@ -0,0 +1,6 @@
|
||||
{
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
@ -0,0 +1,11 @@
|
||||
{
|
||||
"colors" : [
|
||||
{
|
||||
"idiom" : "universal"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
135
ios/SolarWidget/CheckInWidget.swift
Normal file
135
ios/SolarWidget/CheckInWidget.swift
Normal file
@ -0,0 +1,135 @@
|
||||
//
|
||||
// SolarWidget.swift
|
||||
// SolarWidget
|
||||
//
|
||||
// Created by LittleSheep on 2024/12/14.
|
||||
//
|
||||
|
||||
import WidgetKit
|
||||
import SwiftUI
|
||||
|
||||
struct CheckInProvider: TimelineProvider {
|
||||
func placeholder(in context: Context) -> CheckInEntry {
|
||||
CheckInEntry(date: Date(), checkIn: nil)
|
||||
}
|
||||
|
||||
func getSnapshot(in context: Context, completion: @escaping (CheckInEntry) -> ()) {
|
||||
let prefs = UserDefaults(suiteName: "group.solsynth.solian")
|
||||
|
||||
let dateFormatter = DateFormatter()
|
||||
dateFormatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ss.SSSSSS'Z'"
|
||||
|
||||
let jsonDecoder = JSONDecoder()
|
||||
jsonDecoder.dateDecodingStrategy = .formatted(dateFormatter)
|
||||
jsonDecoder.keyDecodingStrategy = .convertFromSnakeCase
|
||||
|
||||
let checkInRaw = prefs?.string(forKey: "pas_check_in_record")
|
||||
var checkIn: SolarCheckInRecord?
|
||||
if let checkInRaw = checkInRaw {
|
||||
checkIn = try! jsonDecoder.decode(SolarCheckInRecord.self, from: checkInRaw.data(using: .utf8)!)
|
||||
if checkIn != nil && !Calendar.current.isDate(checkIn!.createdAt, inSameDayAs: Date()) {
|
||||
checkIn = nil
|
||||
}
|
||||
}
|
||||
|
||||
let entry = CheckInEntry(
|
||||
date: Date(),
|
||||
checkIn: checkIn
|
||||
)
|
||||
completion(entry)
|
||||
}
|
||||
|
||||
func getTimeline(in context: Context, completion: @escaping (Timeline<Entry>) -> ()) {
|
||||
getSnapshot(in: context) { (entry) in
|
||||
let timeline = Timeline(entries: [entry], policy: .atEnd)
|
||||
completion(timeline)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct CheckInEntry: TimelineEntry {
|
||||
let date: Date
|
||||
let checkIn: SolarCheckInRecord?
|
||||
}
|
||||
|
||||
struct CheckInWidgetEntryView : View {
|
||||
var entry: CheckInProvider.Entry
|
||||
|
||||
private let resultTierSymbols: [String] = ["大凶", "凶", "中平", "吉", "大吉"]
|
||||
|
||||
func checkIn() -> Void {}
|
||||
|
||||
func seeDetail() -> Void {}
|
||||
|
||||
var body: some View {
|
||||
VStack(alignment: .leading) {
|
||||
if let checkIn = entry.checkIn {
|
||||
VStack(alignment: .leading) {
|
||||
Text(resultTierSymbols[checkIn.resultTier]).font(.system(size: 27, weight: .bold))
|
||||
Text("+\(checkIn.resultExperience) EXP").font(.system(size: 15, design: .monospaced))
|
||||
}.padding(.horizontal, 4)
|
||||
|
||||
Spacer()
|
||||
|
||||
HStack {
|
||||
VStack(alignment: .leading) {
|
||||
Text(
|
||||
checkIn.createdAt,
|
||||
format: .dateTime.weekday()
|
||||
).font(.system(size: 13))
|
||||
Text(
|
||||
checkIn.createdAt,
|
||||
format: .dateTime.day().month()
|
||||
).font(.system(size: 13))
|
||||
}.padding(.leading, 4)
|
||||
|
||||
Button("See Detail", systemImage: "arrow.right", action: seeDetail)
|
||||
.labelStyle(.iconOnly)
|
||||
.buttonBorderShape(.circle)
|
||||
.frame(maxWidth: .infinity, alignment: .trailing)
|
||||
}.frame(alignment: .bottom)
|
||||
} else {
|
||||
VStack(alignment: .leading) {
|
||||
Text("Check In").font(.system(size: 19, weight: .bold))
|
||||
Text("You haven't check in today").font(.system(size: 15))
|
||||
}.padding(.horizontal, 4)
|
||||
|
||||
Spacer()
|
||||
|
||||
HStack(alignment: .bottom) {
|
||||
Button("Check In", systemImage: "checkmark", action: checkIn).labelStyle(.iconOnly).buttonBorderShape(.circle).frame(maxWidth: .infinity, alignment: .trailing)
|
||||
}
|
||||
}
|
||||
}.padding(8).widgetURL(URL(string: "https://sn.solsynth.dev"))
|
||||
}
|
||||
}
|
||||
|
||||
struct CheckInWidget: Widget {
|
||||
let kind: String = "SolarCheckInWidget"
|
||||
|
||||
var body: some WidgetConfiguration {
|
||||
StaticConfiguration(kind: kind, provider: CheckInProvider()) { entry in
|
||||
if #available(iOS 17.0, *) {
|
||||
CheckInWidgetEntryView(entry: entry)
|
||||
.containerBackground(.fill.tertiary, for: .widget)
|
||||
} else {
|
||||
CheckInWidgetEntryView(entry: entry)
|
||||
.padding()
|
||||
.background()
|
||||
}
|
||||
}
|
||||
.configurationDisplayName("Check In")
|
||||
.description("View your today's fortune on your home screen")
|
||||
.supportedFamilies([.systemSmall, .systemMedium])
|
||||
}
|
||||
}
|
||||
|
||||
#Preview(as: .systemSmall) {
|
||||
CheckInWidget()
|
||||
} timeline: {
|
||||
CheckInEntry(date: .now, checkIn: nil)
|
||||
CheckInEntry(
|
||||
date: .now,
|
||||
checkIn: SolarCheckInRecord(id: 1, resultTier: 1, resultExperience: 100, createdAt: Date.now)
|
||||
)
|
||||
}
|
11
ios/SolarWidget/Info.plist
Normal file
11
ios/SolarWidget/Info.plist
Normal file
@ -0,0 +1,11 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>NSExtension</key>
|
||||
<dict>
|
||||
<key>NSExtensionPointIdentifier</key>
|
||||
<string>com.apple.widgetkit-extension</string>
|
||||
</dict>
|
||||
</dict>
|
||||
</plist>
|
246
ios/SolarWidget/RandomPostWidget.swift
Normal file
246
ios/SolarWidget/RandomPostWidget.swift
Normal file
@ -0,0 +1,246 @@
|
||||
//
|
||||
// RandomPostWidget.swift
|
||||
// Runner
|
||||
//
|
||||
// Created by LittleSheep on 2024/12/14.
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
import WidgetKit
|
||||
import Kingfisher
|
||||
|
||||
struct RandomPostProvider: TimelineProvider {
|
||||
func placeholder(in context: Context) -> RandomPostEntry {
|
||||
RandomPostEntry(date: Date(), user: nil, randomPost: nil, family: .systemMedium)
|
||||
}
|
||||
|
||||
func getSnapshot(in context: Context, completion: @escaping (RandomPostEntry) -> ()) {
|
||||
let prefs = UserDefaults(suiteName: "group.solsynth.solian")
|
||||
|
||||
let dateFormatter = DateFormatter()
|
||||
dateFormatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ss.SSSSSS'Z'"
|
||||
|
||||
let jsonDecoder = JSONDecoder()
|
||||
jsonDecoder.dateDecodingStrategy = .formatted(dateFormatter)
|
||||
jsonDecoder.keyDecodingStrategy = .convertFromSnakeCase
|
||||
|
||||
let userRaw = prefs?.string(forKey: "user")
|
||||
var user: SolarUser?
|
||||
if let userRaw = userRaw {
|
||||
user = try! jsonDecoder.decode(SolarUser.self, from: userRaw.data(using: .utf8)!)
|
||||
}
|
||||
|
||||
let randomPostRaw = prefs?.string(forKey: "int_random_post")
|
||||
var randomPost: SolarPost?
|
||||
if let randomPostRaw = randomPostRaw {
|
||||
randomPost = try! jsonDecoder.decode(SolarPost.self, from: randomPostRaw.data(using: .utf8)!)
|
||||
}
|
||||
|
||||
|
||||
let entry = RandomPostEntry(
|
||||
date: Date(),
|
||||
user: user,
|
||||
randomPost: randomPost,
|
||||
family: context.family
|
||||
)
|
||||
completion(entry)
|
||||
}
|
||||
|
||||
func getTimeline(in context: Context, completion: @escaping (Timeline<Entry>) -> ()) {
|
||||
getSnapshot(in: context) { (entry) in
|
||||
let timeline = Timeline(entries: [entry], policy: .atEnd)
|
||||
completion(timeline)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct RandomPostEntry: TimelineEntry {
|
||||
let date: Date
|
||||
let user: SolarUser?
|
||||
let randomPost: SolarPost?
|
||||
|
||||
let family: WidgetFamily
|
||||
}
|
||||
|
||||
struct RandomPostWidgetEntryView : View {
|
||||
var entry: RandomPostProvider.Entry
|
||||
|
||||
var body: some View {
|
||||
VStack(alignment: .leading, spacing: 0) {
|
||||
if let randomPost = entry.randomPost {
|
||||
VStack(alignment: .leading, spacing: 0) {
|
||||
HStack(alignment: .center) {
|
||||
if let avatar = randomPost.publisher.avatar {
|
||||
let avatarUrl = getAttachmentUrl(for: avatar)
|
||||
let size: CGFloat = 28
|
||||
let scaleProcessor = ResizingImageProcessor(referenceSize: CGSize(width: size, height: size), mode: .aspectFill)
|
||||
|
||||
KFImage.url(URL(string: avatarUrl))
|
||||
.resizable()
|
||||
.setProcessor(scaleProcessor)
|
||||
.fade(duration: 0.25)
|
||||
.placeholder{
|
||||
ProgressView()
|
||||
.progressViewStyle(CircularProgressViewStyle())
|
||||
}
|
||||
.aspectRatio(contentMode: .fill)
|
||||
.frame(width: size, height: size)
|
||||
.cornerRadius(size / 2)
|
||||
|
||||
.frame(width: size, height: size, alignment: .center)
|
||||
}
|
||||
|
||||
Text(randomPost.publisher.nick)
|
||||
.font(.system(size: 15))
|
||||
.opacity(0.9)
|
||||
|
||||
Text("@\(randomPost.publisher.name)")
|
||||
.font(.system(size: 13, design: .monospaced))
|
||||
.opacity(0.9)
|
||||
|
||||
Spacer()
|
||||
}.frame(maxWidth: .infinity).padding(.bottom, 12)
|
||||
|
||||
if randomPost.body.title != nil || randomPost.body.description != nil {
|
||||
VStack(alignment: .leading) {
|
||||
if let title = randomPost.body.title {
|
||||
Text(title)
|
||||
.font(.system(size: 17))
|
||||
}
|
||||
if let description = randomPost.body.description {
|
||||
Text(description)
|
||||
.font(.system(size: 15))
|
||||
}
|
||||
}.padding(.bottom, 8)
|
||||
}
|
||||
|
||||
if let content = randomPost.body.content {
|
||||
if (randomPost.body.title == nil && randomPost.body.description == nil) || entry.family == .systemLarge || entry.family == .systemExtraLarge {
|
||||
Text(
|
||||
(entry.family == .systemLarge || entry.family == .systemExtraLarge) ? content : content.replacingOccurrences(of: "\n", with: " ")
|
||||
)
|
||||
.font(.system(size: 15))
|
||||
} else {
|
||||
Text("\(Image(systemName: "plus")) total \(content.count) characters")
|
||||
.font(.system(size: 11, design: .monospaced))
|
||||
.opacity(0.75)
|
||||
.padding(.top, 1)
|
||||
}
|
||||
}
|
||||
|
||||
if let attachment = randomPost.body.attachments {
|
||||
if attachment.count == 1 {
|
||||
Text("\(Image(systemName: "document.fill")) \(attachment.count) attachment")
|
||||
.font(.system(size: 11, design: .monospaced))
|
||||
.opacity(0.75)
|
||||
.padding(.top, 2)
|
||||
} else if attachment.count > 1 {
|
||||
Text("\(Image(systemName: "document.fill")) \(attachment.count) attachments")
|
||||
.font(.system(size: 11, design: .monospaced))
|
||||
.opacity(0.75)
|
||||
.padding(.top, 2)
|
||||
}
|
||||
}
|
||||
|
||||
Spacer()
|
||||
|
||||
Text(randomPost.publishedAt!, format: .dateTime)
|
||||
.font(.system(size: 11))
|
||||
Text("#\(randomPost.id)")
|
||||
.font(.system(size: 9))
|
||||
}.widgetURL(URL(string: "https://sn.solsynth.dev/posts/\(randomPost.id)"))
|
||||
} else {
|
||||
VStack(alignment: .center) {
|
||||
Text("No Recommendations").font(.system(size: 19, weight: .bold))
|
||||
Text("Open the app to load some random post")
|
||||
.font(.system(size: 15))
|
||||
.multilineTextAlignment(.center)
|
||||
}.frame(alignment: .center)
|
||||
}
|
||||
}.padding(8).frame(maxWidth: .infinity)
|
||||
}
|
||||
}
|
||||
|
||||
struct RandomPostWidget: Widget {
|
||||
let kind: String = "SolarRandomPostWidget"
|
||||
|
||||
var body: some WidgetConfiguration {
|
||||
StaticConfiguration(kind: kind, provider: RandomPostProvider()) { entry in
|
||||
if #available(iOS 17.0, *) {
|
||||
RandomPostWidgetEntryView(entry: entry)
|
||||
.containerBackground(.fill.tertiary, for: .widget)
|
||||
} else {
|
||||
RandomPostWidgetEntryView(entry: entry)
|
||||
.padding()
|
||||
.background()
|
||||
}
|
||||
}
|
||||
.configurationDisplayName("Random Post")
|
||||
.description("View the random post on the Solar Network")
|
||||
.supportedFamilies([.systemSmall, .systemMedium, .systemLarge, .systemExtraLarge])
|
||||
}
|
||||
}
|
||||
|
||||
#Preview(as: .systemSmall) {
|
||||
RandomPostWidget()
|
||||
} timeline: {
|
||||
RandomPostEntry(date: Date.now, user: nil, randomPost: nil, family: .systemLarge)
|
||||
RandomPostEntry(
|
||||
date: .now,
|
||||
user: SolarUser(id: 1, name: "demo", nick: "Deemo"),
|
||||
randomPost: SolarPost(
|
||||
id: 1,
|
||||
body: SolarPostBody(
|
||||
content: "Hello, World",
|
||||
title: nil,
|
||||
description: nil,
|
||||
attachments: ["zb2hiUEmYcnpHfVN"]
|
||||
),
|
||||
publisher: SolarPublisher(
|
||||
id: 1,
|
||||
name: "demo",
|
||||
nick: "Deemo",
|
||||
description: nil,
|
||||
avatar: "IZxCFkJUPKRijFCx",
|
||||
banner: nil,
|
||||
createdAt: .now,
|
||||
updatedAt: .now
|
||||
),
|
||||
publisherId: 1,
|
||||
createdAt: .now,
|
||||
updatedAt: .now,
|
||||
editedAt: nil,
|
||||
publishedAt: .now
|
||||
),
|
||||
family: .systemSmall
|
||||
)
|
||||
RandomPostEntry(
|
||||
date: .now,
|
||||
user: SolarUser(id: 1, name: "demo", nick: "Deemo"),
|
||||
randomPost: SolarPost(
|
||||
id: 1,
|
||||
body: SolarPostBody(
|
||||
content: "Hello, World\nOh wow",
|
||||
title: "Title",
|
||||
description: "Description",
|
||||
attachments: ["zb2hiUEmYcnpHfVN"]
|
||||
),
|
||||
publisher: SolarPublisher(
|
||||
id: 1,
|
||||
name: "demo",
|
||||
nick: "Deemo",
|
||||
description: nil,
|
||||
avatar: "IZxCFkJUPKRijFCx",
|
||||
banner: nil,
|
||||
createdAt: .now,
|
||||
updatedAt: .now
|
||||
),
|
||||
publisherId: 1,
|
||||
createdAt: .now,
|
||||
updatedAt: .now,
|
||||
editedAt: nil,
|
||||
publishedAt: .now
|
||||
),
|
||||
family: .systemLarge
|
||||
)
|
||||
}
|
17
ios/SolarWidget/SolarWidgetBundle.swift
Normal file
17
ios/SolarWidget/SolarWidgetBundle.swift
Normal file
@ -0,0 +1,17 @@
|
||||
//
|
||||
// SolarWidgetBundle.swift
|
||||
// SolarWidget
|
||||
//
|
||||
// Created by LittleSheep on 2024/12/14.
|
||||
//
|
||||
|
||||
import WidgetKit
|
||||
import SwiftUI
|
||||
|
||||
@main
|
||||
struct SolarWidgetBundle: WidgetBundle {
|
||||
var body: some Widget {
|
||||
CheckInWidget()
|
||||
RandomPostWidget()
|
||||
}
|
||||
}
|
10
ios/SolarWidgetExtension.entitlements
Normal file
10
ios/SolarWidgetExtension.entitlements
Normal file
@ -0,0 +1,10 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>com.apple.security.application-groups</key>
|
||||
<array>
|
||||
<string>group.solsynth.solian</string>
|
||||
</array>
|
||||
</dict>
|
||||
</plist>
|
213
lib/main.dart
213
lib/main.dart
@ -1,21 +1,29 @@
|
||||
import 'dart:async';
|
||||
import 'dart:developer';
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:bitsdojo_window/bitsdojo_window.dart';
|
||||
import 'package:croppy/croppy.dart';
|
||||
import 'package:dio/dio.dart';
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:easy_localization_loader/easy_localization_loader.dart';
|
||||
import 'package:firebase_core/firebase_core.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:gap/gap.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
import 'package:hive_flutter/hive_flutter.dart';
|
||||
import 'package:package_info_plus/package_info_plus.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:relative_time/relative_time.dart';
|
||||
import 'package:responsive_framework/responsive_framework.dart';
|
||||
import 'package:sentry_flutter/sentry_flutter.dart';
|
||||
import 'package:shared_preferences/shared_preferences.dart';
|
||||
import 'package:styled_widget/styled_widget.dart';
|
||||
import 'package:surface/firebase_options.dart';
|
||||
import 'package:surface/providers/channel.dart';
|
||||
import 'package:surface/providers/chat_call.dart';
|
||||
import 'package:surface/providers/config.dart';
|
||||
import 'package:surface/providers/link_preview.dart';
|
||||
import 'package:surface/providers/navigation.dart';
|
||||
import 'package:surface/providers/notification.dart';
|
||||
import 'package:surface/providers/post.dart';
|
||||
@ -26,10 +34,33 @@ import 'package:surface/providers/theme.dart';
|
||||
import 'package:surface/providers/user_directory.dart';
|
||||
import 'package:surface/providers/userinfo.dart';
|
||||
import 'package:surface/providers/websocket.dart';
|
||||
import 'package:surface/providers/widget.dart';
|
||||
import 'package:surface/router.dart';
|
||||
import 'package:surface/types/chat.dart';
|
||||
import 'package:surface/types/realm.dart';
|
||||
import 'package:flutter_web_plugins/url_strategy.dart' show usePathUrlStrategy;
|
||||
import 'package:surface/widgets/dialog.dart';
|
||||
import 'package:surface/widgets/version_label.dart';
|
||||
import 'package:version/version.dart';
|
||||
import 'package:workmanager/workmanager.dart';
|
||||
import 'package:in_app_review/in_app_review.dart';
|
||||
|
||||
@pragma('vm:entry-point')
|
||||
void appBackgroundDispatcher() {
|
||||
Workmanager().executeTask((task, inputData) async {
|
||||
log("[WorkManager] Native called background task: $task");
|
||||
switch (task) {
|
||||
case Workmanager.iOSBackgroundTask:
|
||||
await Future.wait([widgetUpdateRandomPost()]);
|
||||
return true;
|
||||
case "WidgetUpdateRandomPost":
|
||||
await widgetUpdateRandomPost();
|
||||
return true;
|
||||
default:
|
||||
return true;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void main() async {
|
||||
WidgetsFlutterBinding.ensureInitialized();
|
||||
@ -57,14 +88,23 @@ void main() async {
|
||||
});
|
||||
}
|
||||
|
||||
await SentryFlutter.init(
|
||||
(options) {
|
||||
options.dsn = 'https://c218d44126d59d69301e730498494def@o4506965897117696.ingest.us.sentry.io/4508346768228352';
|
||||
options.tracesSampleRate = 1.0;
|
||||
options.profilesSampleRate = 1.0;
|
||||
},
|
||||
appRunner: () => runApp(const SolianApp()),
|
||||
);
|
||||
if (!kIsWeb && (Platform.isAndroid || Platform.isIOS)) {
|
||||
Workmanager().initialize(
|
||||
appBackgroundDispatcher,
|
||||
isInDebugMode: kDebugMode,
|
||||
);
|
||||
if (Platform.isAndroid) {
|
||||
Workmanager().registerPeriodicTask(
|
||||
"widget-update-random-post",
|
||||
"WidgetUpdateRandomPost",
|
||||
frequency: Duration(minutes: 1),
|
||||
constraints: Constraints(networkType: NetworkType.connected),
|
||||
tag: "widget-update",
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
runApp(const SolianApp());
|
||||
}
|
||||
|
||||
class SolianApp extends StatelessWidget {
|
||||
@ -86,23 +126,30 @@ class SolianApp extends StatelessWidget {
|
||||
assetLoader: JsonAssetLoader(),
|
||||
child: MultiProvider(
|
||||
providers: [
|
||||
// System extensions layer
|
||||
Provider(create: (ctx) => HomeWidgetProvider(ctx)),
|
||||
|
||||
// Preferences layer
|
||||
ChangeNotifierProvider(create: (ctx) => ConfigProvider(ctx)),
|
||||
|
||||
// Display layer
|
||||
ChangeNotifierProvider(create: (_) => ThemeProvider()),
|
||||
ChangeNotifierProvider(create: (ctx) => NavigationProvider()),
|
||||
|
||||
// Data layer
|
||||
Provider(create: (_) => SnNetworkProvider()),
|
||||
Provider(create: (ctx) => SnNetworkProvider(ctx)),
|
||||
Provider(create: (ctx) => UserDirectoryProvider(ctx)),
|
||||
Provider(create: (ctx) => SnAttachmentProvider(ctx)),
|
||||
Provider(create: (ctx) => SnPostContentProvider(ctx)),
|
||||
Provider(create: (ctx) => SnRelationshipProvider(ctx)),
|
||||
Provider(create: (ctx) => SnLinkPreviewProvider(ctx)),
|
||||
ChangeNotifierProvider(create: (ctx) => UserProvider(ctx)),
|
||||
ChangeNotifierProvider(create: (ctx) => WebSocketProvider(ctx)),
|
||||
ChangeNotifierProvider(create: (ctx) => NotificationProvider(ctx)),
|
||||
ChangeNotifierProvider(create: (ctx) => ChatChannelProvider(ctx)),
|
||||
ChangeNotifierProvider(create: (ctx) => ChatCallProvider(ctx)),
|
||||
],
|
||||
child: AppMainContent(),
|
||||
child: _AppDelegate(),
|
||||
),
|
||||
),
|
||||
breakpoints: [
|
||||
@ -114,8 +161,8 @@ class SolianApp extends StatelessWidget {
|
||||
}
|
||||
}
|
||||
|
||||
class AppMainContent extends StatelessWidget {
|
||||
const AppMainContent({super.key});
|
||||
class _AppDelegate extends StatelessWidget {
|
||||
const _AppDelegate();
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
@ -137,6 +184,146 @@ class AppMainContent extends StatelessWidget {
|
||||
...context.localizationDelegates,
|
||||
],
|
||||
routerConfig: appRouter,
|
||||
builder: (context, child) {
|
||||
return _AppSplashScreen(
|
||||
key: const Key('global-splash-screen'),
|
||||
child: child!,
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _AppSplashScreen extends StatefulWidget {
|
||||
final Widget child;
|
||||
|
||||
const _AppSplashScreen({super.key, required this.child});
|
||||
|
||||
@override
|
||||
State<_AppSplashScreen> createState() => _AppSplashScreenState();
|
||||
}
|
||||
|
||||
class _AppSplashScreenState extends State<_AppSplashScreen> {
|
||||
bool _isReady = false;
|
||||
|
||||
void _tryRequestRating() async {
|
||||
final prefs = await SharedPreferences.getInstance();
|
||||
if (prefs.containsKey('first_boot_time')) {
|
||||
final rawTime = prefs.getString('first_boot_time');
|
||||
final time = DateTime.tryParse(rawTime ?? '');
|
||||
if (time != null && time.isBefore(DateTime.now().subtract(const Duration(days: 3)))) {
|
||||
final inAppReview = InAppReview.instance;
|
||||
if (prefs.getBool('rating_requested') == true) return;
|
||||
if (await inAppReview.isAvailable()) {
|
||||
await inAppReview.requestReview();
|
||||
prefs.setBool('rating_requested', true);
|
||||
} else {
|
||||
log('Unable request app review, unavailable');
|
||||
}
|
||||
}
|
||||
} else {
|
||||
prefs.setString('first_boot_time', DateTime.now().toIso8601String());
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _checkForUpdate() async {
|
||||
if (kIsWeb) return;
|
||||
try {
|
||||
final info = await PackageInfo.fromPlatform();
|
||||
final localVersionString = '${info.version}+${info.buildNumber}';
|
||||
final resp = await Dio(
|
||||
BaseOptions(
|
||||
sendTimeout: const Duration(seconds: 60),
|
||||
receiveTimeout: const Duration(seconds: 60),
|
||||
),
|
||||
).get(
|
||||
'https://git.solsynth.dev/api/v1/repos/HyperNet/Surface/tags?page=1&limit=1',
|
||||
);
|
||||
final remoteVersionString = (resp.data as List).firstOrNull?['name'] ?? '0.0.0+0';
|
||||
final remoteVersion = Version.parse(remoteVersionString.split('+').first);
|
||||
final localVersion = Version.parse(localVersionString.split('+').first);
|
||||
final remoteBuildNumber = int.tryParse(remoteVersionString.split('+').last) ?? 0;
|
||||
final localBuildNumber = int.tryParse(localVersionString.split('+').last) ?? 0;
|
||||
log("[Update] Local: $localVersionString, Remote: $remoteVersionString");
|
||||
if ((remoteVersion > localVersion || remoteBuildNumber > localBuildNumber) && mounted) {
|
||||
final config = context.read<ConfigProvider>();
|
||||
config.setUpdate(remoteVersionString);
|
||||
log("[Update] Update available: $remoteVersionString");
|
||||
}
|
||||
} catch (e) {
|
||||
if (mounted) context.showErrorDialog('Unable to check update: $e');
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _initialize() async {
|
||||
try {
|
||||
final home = context.read<HomeWidgetProvider>();
|
||||
await home.initialize();
|
||||
if (!mounted) return;
|
||||
// The Network initialization must be done after the HomeWidget initialization
|
||||
// The Network initialization will save the server url to the HomeWidget
|
||||
// The Network initialization will also save initialize the Config, so it not need to be initialized again
|
||||
final sn = context.read<SnNetworkProvider>();
|
||||
await sn.initializeUserAgent();
|
||||
if (!mounted) return;
|
||||
final ua = context.read<UserProvider>();
|
||||
await ua.initialize();
|
||||
if (!mounted) return;
|
||||
final ws = context.read<WebSocketProvider>();
|
||||
await ws.tryConnect();
|
||||
if (!mounted) return;
|
||||
final notify = context.read<NotificationProvider>();
|
||||
await notify.registerPushNotifications();
|
||||
} catch (err) {
|
||||
if (!mounted) return;
|
||||
await context.showErrorDialog(err);
|
||||
} finally {
|
||||
setState(() => _isReady = true);
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _postInitialization() async {
|
||||
await widgetUpdateRandomPost();
|
||||
}
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_initialize().then((_) {
|
||||
_postInitialization();
|
||||
_tryRequestRating();
|
||||
_checkForUpdate();
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
if (!_isReady) {
|
||||
return Scaffold(
|
||||
backgroundColor: Theme.of(context).colorScheme.surface,
|
||||
body: Container(
|
||||
constraints: const BoxConstraints(maxWidth: 180),
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
if (MediaQuery.of(context).platformBrightness == Brightness.dark)
|
||||
Image.asset("assets/icon/icon-dark.png", width: 64, height: 64)
|
||||
else
|
||||
Image.asset("assets/icon/icon.png", width: 64, height: 64),
|
||||
const Gap(6),
|
||||
LinearProgressIndicator(
|
||||
backgroundColor: Theme.of(context).colorScheme.surfaceContainer,
|
||||
),
|
||||
const Gap(20),
|
||||
Text('appInitializing'.tr(), textAlign: TextAlign.center),
|
||||
AppVersionLabel(),
|
||||
],
|
||||
),
|
||||
).center(),
|
||||
);
|
||||
}
|
||||
|
||||
return widget.child;
|
||||
}
|
||||
}
|
||||
|
@ -125,10 +125,8 @@ class ChatChannelProvider extends ChangeNotifier {
|
||||
final channelBox = await Hive.openBox<SnChatMessage>(
|
||||
'${ChatMessageController.kChatMessageBoxPrefix}${channel.id}',
|
||||
);
|
||||
final lastMessage = channelBox.isNotEmpty
|
||||
? channelBox.values
|
||||
.reduce((a, b) => a.createdAt.isAfter(b.createdAt) ? a : b)
|
||||
: null;
|
||||
final lastMessage =
|
||||
channelBox.isNotEmpty ? channelBox.values.reduce((a, b) => a.createdAt.isAfter(b.createdAt) ? a : b) : null;
|
||||
if (lastMessage != null) result.add(lastMessage);
|
||||
channelBox.close();
|
||||
}
|
||||
|
51
lib/providers/config.dart
Normal file
51
lib/providers/config.dart
Normal file
@ -0,0 +1,51 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:shared_preferences/shared_preferences.dart';
|
||||
import 'package:surface/providers/widget.dart';
|
||||
|
||||
const kAtkStoreKey = 'nex_user_atk';
|
||||
const kRtkStoreKey = 'nex_user_rtk';
|
||||
|
||||
const kNetworkServerDefault = 'https://api.sn.solsynth.dev';
|
||||
const kNetworkServerStoreKey = 'app_server_url';
|
||||
|
||||
const Map<String, FilterQuality> kImageQualityLevel = {
|
||||
'settingsImageQualityLowest': FilterQuality.none,
|
||||
'settingsImageQualityLow': FilterQuality.low,
|
||||
'settingsImageQualityMedium': FilterQuality.medium,
|
||||
'settingsImageQualityHigh': FilterQuality.high,
|
||||
};
|
||||
|
||||
class ConfigProvider extends ChangeNotifier {
|
||||
late final SharedPreferences prefs;
|
||||
|
||||
late final HomeWidgetProvider _home;
|
||||
|
||||
ConfigProvider(BuildContext context) {
|
||||
_home = context.read<HomeWidgetProvider>();
|
||||
}
|
||||
|
||||
Future<void> initialize() async {
|
||||
prefs = await SharedPreferences.getInstance();
|
||||
}
|
||||
|
||||
FilterQuality get imageQuality {
|
||||
return kImageQualityLevel.values.elementAtOrNull(prefs.getInt('app_image_quality') ?? 3) ?? FilterQuality.high;
|
||||
}
|
||||
|
||||
String get serverUrl {
|
||||
return prefs.getString(kNetworkServerStoreKey) ?? kNetworkServerDefault;
|
||||
}
|
||||
|
||||
set serverUrl(String url) {
|
||||
prefs.setString(kNetworkServerStoreKey, url);
|
||||
_home.saveWidgetData("nex_server_url", url);
|
||||
}
|
||||
|
||||
String? updatableVersion;
|
||||
|
||||
void setUpdate(String newVersion) {
|
||||
updatableVersion = newVersion;
|
||||
notifyListeners();
|
||||
}
|
||||
}
|
35
lib/providers/link_preview.dart
Normal file
35
lib/providers/link_preview.dart
Normal file
@ -0,0 +1,35 @@
|
||||
import 'dart:convert';
|
||||
import 'dart:developer';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:surface/providers/sn_network.dart';
|
||||
import 'package:surface/types/link.dart';
|
||||
|
||||
class SnLinkPreviewProvider {
|
||||
late final SnNetworkProvider _sn;
|
||||
|
||||
final Map<String, SnLinkMeta> _cache = {};
|
||||
|
||||
SnLinkPreviewProvider(BuildContext context) {
|
||||
_sn = context.read<SnNetworkProvider>();
|
||||
}
|
||||
|
||||
Future<SnLinkMeta?> getLinkMeta(String url) async {
|
||||
final b64 = utf8.fuse(base64Url);
|
||||
final target = b64.encode(url);
|
||||
if (_cache.containsKey(target)) return _cache[target];
|
||||
|
||||
log('[LinkPreview] Fetching $url ($target)');
|
||||
|
||||
try {
|
||||
final resp = await _sn.client.get('/cgi/re/link/$target');
|
||||
final meta = SnLinkMeta.fromJson(resp.data);
|
||||
_cache[url] = meta;
|
||||
return meta;
|
||||
} catch (err) {
|
||||
log('[LinkPreview] Failed to fetch $url ($target)...');
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
@ -16,14 +16,6 @@ class NotificationProvider extends ChangeNotifier {
|
||||
NotificationProvider(BuildContext context) {
|
||||
_sn = context.read<SnNetworkProvider>();
|
||||
_ua = context.read<UserProvider>();
|
||||
|
||||
// Delay to wait user provider ready to use
|
||||
Future.delayed(const Duration(milliseconds: 3000), () async {
|
||||
if (!_ua.isAuthorized) return;
|
||||
log("Registering push notifications...");
|
||||
await registerPushNotifications();
|
||||
log("Registered push notification subscriber successfully!");
|
||||
});
|
||||
}
|
||||
|
||||
Future<void> registerPushNotifications() async {
|
||||
|
@ -23,6 +23,11 @@ class SnPostContentProvider {
|
||||
if (out[i].body['thumbnail'] != null) {
|
||||
rids.add(out[i].body['thumbnail']);
|
||||
}
|
||||
if (out[i].repostTo != null) {
|
||||
out[i] = out[i].copyWith(
|
||||
repostTo: await _preloadRelatedDataSingle(out[i].repostTo!),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
final attachments = await _attach.getMultiple(rids.toList());
|
||||
@ -48,6 +53,11 @@ class SnPostContentProvider {
|
||||
if (out.body['thumbnail'] != null) {
|
||||
rids.add(out.body['thumbnail']);
|
||||
}
|
||||
if (out.repostTo != null) {
|
||||
out = out.copyWith(
|
||||
repostTo: await _preloadRelatedDataSingle(out.repostTo!),
|
||||
);
|
||||
}
|
||||
|
||||
final attachments = await _attach.getMultiple(rids.toList());
|
||||
out = out.copyWith(
|
||||
|
@ -1,6 +1,7 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:surface/providers/sn_network.dart';
|
||||
import 'package:surface/types/account.dart';
|
||||
|
||||
class SnRelationshipProvider {
|
||||
late final SnNetworkProvider _sn;
|
||||
@ -9,6 +10,15 @@ class SnRelationshipProvider {
|
||||
_sn = context.read<SnNetworkProvider>();
|
||||
}
|
||||
|
||||
Future<SnRelationship?> getRelationship(int relatedId) async {
|
||||
try {
|
||||
final resp = await _sn.client.get('/cgi/id/users/me/relations/$relatedId');
|
||||
return SnRelationship.fromJson(resp.data);
|
||||
} catch (err) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> updateRelationship(
|
||||
int relatedId,
|
||||
int status,
|
||||
|
@ -1,29 +1,39 @@
|
||||
import 'dart:async';
|
||||
import 'dart:convert';
|
||||
import 'dart:developer';
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:dio/dio.dart';
|
||||
import 'package:dio_smart_retry/dio_smart_retry.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:package_info_plus/package_info_plus.dart';
|
||||
import 'package:device_info_plus/device_info_plus.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:shared_preferences/shared_preferences.dart';
|
||||
import 'package:surface/providers/config.dart';
|
||||
import 'package:surface/providers/widget.dart';
|
||||
import 'package:synchronized/synchronized.dart';
|
||||
|
||||
const kAtkStoreKey = 'nex_user_atk';
|
||||
const kRtkStoreKey = 'nex_user_rtk';
|
||||
|
||||
const kNetworkServerDefault = 'https://api.sn.solsynth.dev';
|
||||
const kNetworkServerStoreKey = 'app_server_url';
|
||||
|
||||
const kNetworkServerDirectory = [
|
||||
('Solar Network', 'https://api.sn.solsynth.dev'),
|
||||
('Local', 'http://localhost:8001'),
|
||||
];
|
||||
|
||||
Completer<String?>? _refreshCompleter;
|
||||
|
||||
class SnNetworkProvider {
|
||||
late final Dio client;
|
||||
|
||||
late final SharedPreferences _prefs;
|
||||
late final ConfigProvider _config;
|
||||
late final HomeWidgetProvider _home;
|
||||
|
||||
String? _userAgent;
|
||||
|
||||
SnNetworkProvider(BuildContext context) {
|
||||
_home = context.read<HomeWidgetProvider>();
|
||||
|
||||
SnNetworkProvider() {
|
||||
client = Dio();
|
||||
|
||||
client.interceptors.add(RetryInterceptor(
|
||||
@ -46,23 +56,101 @@ class SnNetworkProvider {
|
||||
if (atk != null) {
|
||||
options.headers['Authorization'] = 'Bearer $atk';
|
||||
}
|
||||
if (_userAgent != null) {
|
||||
options.headers['User-Agent'] = _userAgent!;
|
||||
}
|
||||
return handler.next(options);
|
||||
},
|
||||
),
|
||||
);
|
||||
|
||||
SharedPreferences.getInstance().then((prefs) {
|
||||
_prefs = prefs;
|
||||
client.options.baseUrl =
|
||||
_prefs.getString(kNetworkServerStoreKey) ?? kNetworkServerDefault;
|
||||
_config = context.read<ConfigProvider>();
|
||||
_config.initialize().then((_) {
|
||||
_prefs = _config.prefs;
|
||||
client.options.baseUrl = _config.serverUrl;
|
||||
if (!context.mounted) return;
|
||||
_home.saveWidgetData("nex_server_url", client.options.baseUrl);
|
||||
});
|
||||
}
|
||||
|
||||
static Future<Dio> createOffContextClient() async {
|
||||
final prefs = await SharedPreferences.getInstance();
|
||||
final client = Dio();
|
||||
client.interceptors.add(RetryInterceptor(
|
||||
dio: client,
|
||||
retries: 3,
|
||||
retryDelays: const [
|
||||
Duration(milliseconds: 300),
|
||||
Duration(milliseconds: 1000),
|
||||
Duration(milliseconds: 3000),
|
||||
],
|
||||
));
|
||||
final ua = await _getUserAgent();
|
||||
client.interceptors.add(
|
||||
InterceptorsWrapper(
|
||||
onRequest: (
|
||||
RequestOptions options,
|
||||
RequestInterceptorHandler handler,
|
||||
) async {
|
||||
final atk = await _getFreshAtk(client, prefs.getString(kAtkStoreKey), prefs.getString(kRtkStoreKey), (atk, rtk) {
|
||||
prefs.setString(kAtkStoreKey, atk);
|
||||
prefs.setString(kRtkStoreKey, rtk);
|
||||
});
|
||||
if (atk != null) {
|
||||
options.headers['Authorization'] = 'Bearer $atk';
|
||||
}
|
||||
options.headers['User-Agent'] = ua;
|
||||
return handler.next(options);
|
||||
},
|
||||
),
|
||||
);
|
||||
client.options.baseUrl = prefs.getString(kNetworkServerStoreKey) ?? kNetworkServerDefault;
|
||||
|
||||
return client;
|
||||
}
|
||||
|
||||
static Future<String> _getUserAgent() async {
|
||||
final String platformInfo;
|
||||
if (kIsWeb) {
|
||||
final deviceInfo = await DeviceInfoPlugin().webBrowserInfo;
|
||||
platformInfo = 'Web; ${deviceInfo.vendor}';
|
||||
} else if (Platform.isAndroid) {
|
||||
final deviceInfo = await DeviceInfoPlugin().androidInfo;
|
||||
platformInfo = 'Android; ${deviceInfo.brand} ${deviceInfo.model}; ${deviceInfo.id}';
|
||||
} else if (Platform.isIOS) {
|
||||
final deviceInfo = await DeviceInfoPlugin().iosInfo;
|
||||
platformInfo = 'iOS; ${deviceInfo.model}; ${deviceInfo.name}';
|
||||
} else if (Platform.isMacOS) {
|
||||
final deviceInfo = await DeviceInfoPlugin().macOsInfo;
|
||||
platformInfo = 'MacOS; ${deviceInfo.model}; ${deviceInfo.hostName}';
|
||||
} else if (Platform.isWindows) {
|
||||
final deviceInfo = await DeviceInfoPlugin().windowsInfo;
|
||||
platformInfo = 'Windows NT; ${deviceInfo.productName}; ${deviceInfo.computerName}';
|
||||
} else if (Platform.isLinux) {
|
||||
final deviceInfo = await DeviceInfoPlugin().linuxInfo;
|
||||
platformInfo = 'Linux; ${deviceInfo.prettyName}';
|
||||
} else {
|
||||
platformInfo = 'Unknown';
|
||||
}
|
||||
|
||||
final packageInfo = await PackageInfo.fromPlatform();
|
||||
|
||||
return 'Solian/${packageInfo.version}+${packageInfo.buildNumber} ($platformInfo)';
|
||||
}
|
||||
|
||||
Future<void> initializeUserAgent() async {
|
||||
_userAgent = await _getUserAgent();
|
||||
}
|
||||
|
||||
final tkLock = Lock();
|
||||
|
||||
Completer<String?>? _refreshCompleter;
|
||||
|
||||
Future<String?> getFreshAtk() async {
|
||||
return await _getFreshAtk(client, _prefs.getString(kAtkStoreKey), _prefs.getString(kRtkStoreKey), (atk, rtk) {
|
||||
setTokenPair(atk, rtk);
|
||||
});
|
||||
}
|
||||
|
||||
static Future<String?> _getFreshAtk(Dio client, String? atk, String? rtk, Function(String atk, String rtk)? onRefresh) async {
|
||||
if (_refreshCompleter != null) {
|
||||
return await _refreshCompleter!.future;
|
||||
} else {
|
||||
@ -70,7 +158,6 @@ class SnNetworkProvider {
|
||||
}
|
||||
|
||||
try {
|
||||
var atk = _prefs.getString(kAtkStoreKey);
|
||||
if (atk != null) {
|
||||
final atkParts = atk.split('.');
|
||||
if (atkParts.length != 3) {
|
||||
@ -96,7 +183,13 @@ class SnNetworkProvider {
|
||||
final exp = jsonDecode(payload)['exp'];
|
||||
if (exp <= DateTime.now().millisecondsSinceEpoch ~/ 1000) {
|
||||
log('Access token need refresh, doing it at ${DateTime.now()}');
|
||||
atk = await refreshToken();
|
||||
final result = await _refreshToken(client.options.baseUrl, rtk);
|
||||
if (result == null) {
|
||||
atk = null;
|
||||
} else {
|
||||
atk = result.$1;
|
||||
onRefresh?.call(atk, result.$2);
|
||||
}
|
||||
}
|
||||
|
||||
if (atk != null) {
|
||||
@ -134,24 +227,32 @@ class SnNetworkProvider {
|
||||
|
||||
Future<String?> refreshToken() async {
|
||||
final rtk = _prefs.getString(kRtkStoreKey);
|
||||
final result = await _refreshToken(client.options.baseUrl, rtk);
|
||||
if (result == null) return null;
|
||||
_prefs.setString(kAtkStoreKey, result.$1);
|
||||
_prefs.setString(kRtkStoreKey, result.$2);
|
||||
return result.$1;
|
||||
}
|
||||
|
||||
static Future<(String, String)?> _refreshToken(String baseUrl, String? rtk) async {
|
||||
if (rtk == null) return null;
|
||||
|
||||
final dio = Dio();
|
||||
dio.options.baseUrl = client.options.baseUrl;
|
||||
dio.options.baseUrl = baseUrl;
|
||||
|
||||
final resp = await dio.post('/cgi/id/auth/token', data: {
|
||||
'grant_type': 'refresh_token',
|
||||
'refresh_token': rtk,
|
||||
});
|
||||
|
||||
final atk = resp.data['access_token'];
|
||||
final nRtk = resp.data['refresh_token'];
|
||||
setTokenPair(atk, nRtk);
|
||||
final String atk = resp.data['access_token'];
|
||||
final String nRtk = resp.data['refresh_token'];
|
||||
|
||||
return atk;
|
||||
return (atk, nRtk);
|
||||
}
|
||||
|
||||
void setBaseUrl(String url) {
|
||||
_config.serverUrl = url;
|
||||
client.options.baseUrl = url;
|
||||
}
|
||||
}
|
||||
|
@ -1,9 +1,12 @@
|
||||
import 'dart:developer';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:path/path.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:shared_preferences/shared_preferences.dart';
|
||||
import 'package:surface/providers/config.dart';
|
||||
import 'package:surface/providers/sn_network.dart';
|
||||
import 'package:surface/providers/widget.dart';
|
||||
import 'package:surface/types/account.dart';
|
||||
|
||||
class UserProvider extends ChangeNotifier {
|
||||
@ -11,24 +14,29 @@ class UserProvider extends ChangeNotifier {
|
||||
SnAccount? user;
|
||||
|
||||
late final SnNetworkProvider _sn;
|
||||
late final HomeWidgetProvider _home;
|
||||
late final ConfigProvider _config;
|
||||
|
||||
UserProvider(BuildContext context) {
|
||||
_sn = context.read<SnNetworkProvider>();
|
||||
_home = context.read<HomeWidgetProvider>();
|
||||
_config = context.read<ConfigProvider>();
|
||||
}
|
||||
|
||||
Future<String?> get atk async {
|
||||
final prefs = await SharedPreferences.getInstance();
|
||||
return prefs.getString(kAtkStoreKey);
|
||||
}
|
||||
|
||||
UserProvider(BuildContext context) {
|
||||
_sn = context.read<SnNetworkProvider>();
|
||||
|
||||
SharedPreferences.getInstance().then((prefs) {
|
||||
final value = prefs.getString(kAtkStoreKey);
|
||||
isAuthorized = value != null;
|
||||
notifyListeners();
|
||||
refreshUser().then((value) {
|
||||
if (value != null) {
|
||||
log('Logged in as @${value.name}');
|
||||
}
|
||||
});
|
||||
Future<void> initialize() async {
|
||||
final value = _config.prefs.getString(kAtkStoreKey);
|
||||
isAuthorized = value != null;
|
||||
notifyListeners();
|
||||
refreshUser().then((value) {
|
||||
if (value != null) {
|
||||
log('Logged in as @${value.name}');
|
||||
_home.saveWidgetData('user', value.toJson());
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -23,16 +23,14 @@ class WebSocketProvider extends ChangeNotifier {
|
||||
WebSocketProvider(BuildContext context) {
|
||||
_sn = context.read<SnNetworkProvider>();
|
||||
_ua = context.read<UserProvider>();
|
||||
}
|
||||
|
||||
// Wait for the userinfo provide initialize authorization status
|
||||
Future.delayed(const Duration(milliseconds: 250), () async {
|
||||
if (_ua.isAuthorized) {
|
||||
log('[WebSocket] Connecting to the server...');
|
||||
await connect();
|
||||
} else {
|
||||
log('[WebSocket] Unable connect to the server, unauthorized.');
|
||||
}
|
||||
});
|
||||
Future<void> tryConnect() async {
|
||||
if (isConnected) return;
|
||||
if (!_ua.isAuthorized) return;
|
||||
|
||||
log('[WebSocket] Connecting to the server...');
|
||||
await connect();
|
||||
}
|
||||
|
||||
Future<void> connect({noRetry = false}) async {
|
||||
@ -79,6 +77,7 @@ class WebSocketProvider extends ChangeNotifier {
|
||||
if (conn != null) {
|
||||
conn!.sink.close();
|
||||
}
|
||||
conn = null;
|
||||
isConnected = false;
|
||||
notifyListeners();
|
||||
}
|
||||
|
60
lib/providers/widget.dart
Normal file
60
lib/providers/widget.dart
Normal file
@ -0,0 +1,60 @@
|
||||
import 'dart:async';
|
||||
import 'dart:convert';
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:home_widget/home_widget.dart';
|
||||
import 'package:surface/providers/sn_network.dart';
|
||||
import 'package:surface/types/post.dart';
|
||||
|
||||
class HomeWidgetProvider {
|
||||
HomeWidgetProvider(BuildContext context);
|
||||
|
||||
Future<void> initialize() async {
|
||||
if (kIsWeb || !(Platform.isAndroid || Platform.isIOS)) return;
|
||||
if (!kIsWeb && Platform.isIOS) {
|
||||
await HomeWidget.setAppGroupId("group.solsynth.solian");
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> saveWidgetData(String id, dynamic data, {bool update = true}) async {
|
||||
if (kIsWeb || !(Platform.isAndroid || Platform.isIOS)) return;
|
||||
await HomeWidget.saveWidgetData(id, jsonEncode(data));
|
||||
if (update) await updateWidget();
|
||||
}
|
||||
|
||||
Future<void> updateWidget() async {
|
||||
if (kIsWeb || !(Platform.isAndroid || Platform.isIOS)) return;
|
||||
if (Platform.isIOS) {
|
||||
const widgets = ["SolarRandomPostWidget", "SolarCheckInWidget"];
|
||||
for (final widget in widgets) {
|
||||
await HomeWidget.updateWidget(
|
||||
name: widget,
|
||||
iOSName: widget,
|
||||
);
|
||||
}
|
||||
} else if (Platform.isAndroid) {
|
||||
const widgets = ["RandomPostWidget", "CheckInWidget"];
|
||||
for (final widget in widgets) {
|
||||
await HomeWidget.updateWidget(
|
||||
androidName: "${widget}Receiver",
|
||||
qualifiedAndroidName: "dev.solsynth.solian.widgets.${widget}Receiver",
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> widgetUpdateRandomPost() async {
|
||||
final snc = await SnNetworkProvider.createOffContextClient();
|
||||
final resp = await snc.get('/cgi/co/recommendations/shuffle?take=1');
|
||||
final post = SnPost.fromJson(resp.data['data'][0]);
|
||||
await HomeWidget.saveWidgetData("int_random_post", jsonEncode(post.toJson()));
|
||||
await HomeWidget.updateWidget(
|
||||
name: "SolarRandomPostWidget",
|
||||
iOSName: "SolarRandomPostWidget",
|
||||
androidName: "RandomPostWidgetReceiver",
|
||||
qualifiedAndroidName: "dev.solsynth.solian.widgets.RandomPostWidgetReceiver",
|
||||
);
|
||||
}
|
@ -28,6 +28,7 @@ import 'package:surface/screens/realm.dart';
|
||||
import 'package:surface/screens/realm/manage.dart';
|
||||
import 'package:surface/screens/realm/realm_detail.dart';
|
||||
import 'package:surface/screens/settings.dart';
|
||||
import 'package:surface/screens/sharing.dart';
|
||||
import 'package:surface/types/post.dart';
|
||||
import 'package:surface/widgets/about.dart';
|
||||
import 'package:surface/widgets/navigation/app_background.dart';
|
||||
@ -58,7 +59,6 @@ final _appRoutes = [
|
||||
path: '/write/:mode',
|
||||
name: 'postEditor',
|
||||
builder: (context, state) => AppBackground(
|
||||
isLessOptimization: true,
|
||||
child: PostEditorScreen(
|
||||
mode: state.pathParameters['mode']!,
|
||||
postEditId: int.tryParse(
|
||||
@ -70,6 +70,7 @@ final _appRoutes = [
|
||||
postRepostId: int.tryParse(
|
||||
state.uri.queryParameters['reposting'] ?? '',
|
||||
),
|
||||
extraProps: state.extra as PostEditorExtraProps?,
|
||||
),
|
||||
),
|
||||
),
|
||||
@ -77,7 +78,6 @@ final _appRoutes = [
|
||||
path: '/search',
|
||||
name: 'postSearch',
|
||||
builder: (context, state) => const AppBackground(
|
||||
isLessOptimization: true,
|
||||
child: PostSearchScreen(),
|
||||
),
|
||||
),
|
||||
@ -119,7 +119,6 @@ final _appRoutes = [
|
||||
path: '/:scope/:alias',
|
||||
name: 'chatRoom',
|
||||
builder: (context, state) => AppBackground(
|
||||
isLessOptimization: true,
|
||||
child: ChatRoomScreen(
|
||||
scope: state.pathParameters['scope']!,
|
||||
alias: state.pathParameters['alias']!,
|
||||
@ -159,7 +158,6 @@ final _appRoutes = [
|
||||
secondaryAnimation: secondaryAnimation,
|
||||
fillColor: Colors.transparent,
|
||||
child: AppBackground(
|
||||
isLessOptimization: true,
|
||||
child: child,
|
||||
),
|
||||
);
|
||||
@ -195,7 +193,6 @@ final _appRoutes = [
|
||||
secondaryAnimation: secondaryAnimation,
|
||||
fillColor: Colors.transparent,
|
||||
child: AppBackground(
|
||||
isLessOptimization: true,
|
||||
child: child,
|
||||
),
|
||||
);
|
||||
@ -320,7 +317,9 @@ final appRouter = GoRouter(
|
||||
routes: [
|
||||
ShellRoute(
|
||||
routes: _appRoutes,
|
||||
builder: (context, state, child) => AppRootScaffold(body: child),
|
||||
builder: (context, state, child) => AppRootScaffold(
|
||||
body: AppSharingListener(child: child),
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
|
@ -39,7 +39,7 @@ class _AbuseReportScreenState extends State<AbuseReportScreen> {
|
||||
void _showAbuseReportDialog() {
|
||||
showDialog(
|
||||
context: context,
|
||||
builder: (context) => _AbuseReportDialog(),
|
||||
builder: (context) => AbuseReportDialog(),
|
||||
).then((value) {
|
||||
if (value == true && mounted) {
|
||||
_fetchReports();
|
||||
@ -91,19 +91,29 @@ class _AbuseReportScreenState extends State<AbuseReportScreen> {
|
||||
}
|
||||
}
|
||||
|
||||
class _AbuseReportDialog extends StatefulWidget {
|
||||
const _AbuseReportDialog({super.key});
|
||||
class AbuseReportDialog extends StatefulWidget {
|
||||
final String? resourceLocation;
|
||||
|
||||
const AbuseReportDialog({super.key, this.resourceLocation});
|
||||
|
||||
@override
|
||||
State<_AbuseReportDialog> createState() => _AbuseReportDialogState();
|
||||
State<AbuseReportDialog> createState() => _AbuseReportDialogState();
|
||||
}
|
||||
|
||||
class _AbuseReportDialogState extends State<_AbuseReportDialog> {
|
||||
class _AbuseReportDialogState extends State<AbuseReportDialog> {
|
||||
bool _isBusy = false;
|
||||
|
||||
final _resourceController = TextEditingController();
|
||||
final _reasonController = TextEditingController();
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
if (widget.resourceLocation != null) {
|
||||
_resourceController.text = widget.resourceLocation!;
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
dispose() {
|
||||
_resourceController.dispose();
|
||||
@ -144,6 +154,7 @@ class _AbuseReportDialogState extends State<_AbuseReportDialog> {
|
||||
const Gap(12),
|
||||
TextField(
|
||||
controller: _resourceController,
|
||||
readOnly: widget.resourceLocation != null,
|
||||
maxLength: null,
|
||||
decoration: InputDecoration(
|
||||
border: const UnderlineInputBorder(),
|
||||
|
@ -2,7 +2,7 @@ import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:gap/gap.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
import 'package:hive/hive.dart';
|
||||
import 'package:hive_flutter/hive_flutter.dart';
|
||||
import 'package:material_symbols_icons/symbols.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:styled_widget/styled_widget.dart';
|
||||
@ -118,19 +118,19 @@ class _AuthorizedAccountScreen extends StatelessWidget {
|
||||
contentPadding: const EdgeInsets.symmetric(horizontal: 24),
|
||||
leading: const Icon(Symbols.logout),
|
||||
trailing: const Icon(Symbols.chevron_right),
|
||||
onTap: () {
|
||||
context
|
||||
.showConfirmDialog(
|
||||
onTap: () async {
|
||||
final confirm = await context.showConfirmDialog(
|
||||
'accountLogoutConfirmTitle'.tr(),
|
||||
'accountLogoutConfirm'.tr(),
|
||||
)
|
||||
.then((value) {
|
||||
if(!context.mounted) return;
|
||||
if (value) ua.logoutUser();
|
||||
final ws = context.read<WebSocketProvider>();
|
||||
ws.disconnect();
|
||||
Hive.deleteFromDisk();
|
||||
});
|
||||
);
|
||||
|
||||
if (!confirm) return;
|
||||
if (!context.mounted) return;
|
||||
ua.logoutUser();
|
||||
final ws = context.read<WebSocketProvider>();
|
||||
ws.disconnect();
|
||||
await Hive.deleteFromDisk();
|
||||
await Hive.initFlutter();
|
||||
},
|
||||
),
|
||||
ListTile(
|
||||
|
@ -3,13 +3,17 @@ import 'dart:ui';
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:gap/gap.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
import 'package:google_fonts/google_fonts.dart';
|
||||
import 'package:material_symbols_icons/symbols.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:relative_time/relative_time.dart';
|
||||
import 'package:styled_widget/styled_widget.dart';
|
||||
import 'package:surface/providers/relationship.dart';
|
||||
import 'package:surface/providers/sn_network.dart';
|
||||
import 'package:surface/screens/abuse_report.dart';
|
||||
import 'package:surface/types/account.dart';
|
||||
import 'package:surface/types/post.dart';
|
||||
import 'package:surface/widgets/account/account_image.dart';
|
||||
import 'package:surface/widgets/dialog.dart';
|
||||
import 'package:surface/widgets/universal_image.dart';
|
||||
@ -73,6 +77,98 @@ class _UserScreenState extends State<UserScreen> with SingleTickerProviderStateM
|
||||
}
|
||||
}
|
||||
|
||||
List<SnPublisher>? _publishers;
|
||||
|
||||
Future<void> _fetchPublishers() async {
|
||||
try {
|
||||
final sn = context.read<SnNetworkProvider>();
|
||||
final resp = await sn.client.get('/cgi/co/publishers?user=${widget.name}');
|
||||
_publishers = List<SnPublisher>.from(
|
||||
resp.data?.map((e) => SnPublisher.fromJson(e)) ?? [],
|
||||
);
|
||||
} catch (err) {
|
||||
if (mounted) context.showErrorDialog(err);
|
||||
rethrow;
|
||||
} finally {
|
||||
setState(() {});
|
||||
}
|
||||
}
|
||||
|
||||
bool _isBusy = false;
|
||||
SnRelationship? _accountRelationship;
|
||||
|
||||
Future<void> _addFriend() async {
|
||||
if (_isBusy) return;
|
||||
|
||||
setState(() => _isBusy = true);
|
||||
|
||||
try {
|
||||
final sn = context.read<SnNetworkProvider>();
|
||||
await sn.client.post('/cgi/id/users/me/relations/friend', data: {
|
||||
'related': _account!.name,
|
||||
});
|
||||
if (!mounted) return;
|
||||
context.showSnackbar('friendRequestSent'.tr());
|
||||
} catch (err) {
|
||||
if (!mounted) return;
|
||||
context.showErrorDialog(err);
|
||||
} finally {
|
||||
setState(() => _isBusy = false);
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _blockAccount() async {
|
||||
if (_isBusy) return;
|
||||
|
||||
setState(() => _isBusy = true);
|
||||
|
||||
try {
|
||||
final sn = context.read<SnNetworkProvider>();
|
||||
await sn.client.post('/cgi/id/users/me/relations/block', data: {
|
||||
'related': _account!.name,
|
||||
});
|
||||
if (!mounted) return;
|
||||
context.showSnackbar('userBlocked'.tr(args: ['@${_account?.name ?? 'unknown'}']));
|
||||
} catch (err) {
|
||||
if (!mounted) return;
|
||||
context.showErrorDialog(err);
|
||||
} finally {
|
||||
setState(() => _isBusy = false);
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _unblockAccount() async {
|
||||
if (_isBusy) return;
|
||||
|
||||
setState(() => _isBusy = true);
|
||||
|
||||
try {
|
||||
final rel = context.read<SnRelationshipProvider>();
|
||||
await rel.updateRelationship(_account!.id, 1, _accountRelationship?.permNodes ?? {});
|
||||
if (!mounted) return;
|
||||
context.showSnackbar('userUnblocked'.tr(args: ['@${_account?.name ?? 'unknown'}']));
|
||||
} catch (err) {
|
||||
if (!mounted) return;
|
||||
context.showErrorDialog(err);
|
||||
} finally {
|
||||
setState(() => _isBusy = false);
|
||||
}
|
||||
}
|
||||
|
||||
void _showAbuseReportDialog() {
|
||||
showDialog(
|
||||
context: context,
|
||||
builder: (context) => AbuseReportDialog(
|
||||
resourceLocation: 'user:${_account?.name}',
|
||||
),
|
||||
).then((value) {
|
||||
if (value == true && mounted) {
|
||||
_fetchAccount();
|
||||
context.showSnackbar('abuseReportSubmitted'.tr());
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
double _appBarBlur = 0.0;
|
||||
|
||||
late final _appBarWidth = MediaQuery.of(context).size.width;
|
||||
@ -88,8 +184,19 @@ class _UserScreenState extends State<UserScreen> with SingleTickerProviderStateM
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_fetchAccount().then((_) {
|
||||
_fetchAccount().then((_) async {
|
||||
if (!mounted) return;
|
||||
|
||||
_fetchStatus();
|
||||
_fetchPublishers();
|
||||
|
||||
try {
|
||||
final rel = context.read<SnRelationshipProvider>();
|
||||
_accountRelationship = await rel.getRelationship(_account!.id);
|
||||
if (mounted) setState(() {});
|
||||
} catch (_) {
|
||||
// ignore
|
||||
}
|
||||
});
|
||||
_scrollController.addListener(_updateAppBarBlur);
|
||||
}
|
||||
@ -131,7 +238,7 @@ class _UserScreenState extends State<UserScreen> with SingleTickerProviderStateM
|
||||
TextSpan(
|
||||
text: _account!.nick,
|
||||
style: Theme.of(context).textTheme.titleLarge!.copyWith(
|
||||
color: Colors.white,
|
||||
color: Theme.of(context).appBarTheme.foregroundColor!,
|
||||
shadows: labelShadows,
|
||||
),
|
||||
),
|
||||
@ -139,7 +246,7 @@ class _UserScreenState extends State<UserScreen> with SingleTickerProviderStateM
|
||||
TextSpan(
|
||||
text: '@${_account!.name}',
|
||||
style: Theme.of(context).textTheme.bodySmall!.copyWith(
|
||||
color: Colors.white,
|
||||
color: Theme.of(context).appBarTheme.foregroundColor!,
|
||||
shadows: labelShadows,
|
||||
),
|
||||
),
|
||||
@ -205,6 +312,57 @@ class _UserScreenState extends State<UserScreen> with SingleTickerProviderStateM
|
||||
],
|
||||
),
|
||||
),
|
||||
PopupMenuButton(
|
||||
padding: EdgeInsets.zero,
|
||||
style: ButtonStyle(
|
||||
visualDensity: VisualDensity(horizontal: -4, vertical: -4),
|
||||
),
|
||||
itemBuilder: (context) => [
|
||||
PopupMenuItem(
|
||||
onTap: _showAbuseReportDialog,
|
||||
child: Row(
|
||||
children: [
|
||||
const Icon(Symbols.flag),
|
||||
const Gap(16),
|
||||
Text('report').tr(),
|
||||
],
|
||||
),
|
||||
),
|
||||
if (_accountRelationship == null)
|
||||
PopupMenuItem(
|
||||
onTap: _addFriend,
|
||||
child: Row(
|
||||
children: [
|
||||
const Icon(Symbols.person_add),
|
||||
const Gap(16),
|
||||
Text('friendNew').tr(),
|
||||
],
|
||||
),
|
||||
),
|
||||
if (_accountRelationship?.status != 2)
|
||||
PopupMenuItem(
|
||||
onTap: _blockAccount,
|
||||
child: Row(
|
||||
children: [
|
||||
const Icon(Symbols.block),
|
||||
const Gap(16),
|
||||
Text('friendBlock').tr(),
|
||||
],
|
||||
),
|
||||
)
|
||||
else
|
||||
PopupMenuItem(
|
||||
onTap: _unblockAccount,
|
||||
child: Row(
|
||||
children: [
|
||||
const Icon(Symbols.block),
|
||||
const Gap(16),
|
||||
Text('friendUnblock').tr(),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
).padding(right: 8),
|
||||
const Gap(12),
|
||||
@ -346,7 +504,31 @@ class _UserScreenState extends State<UserScreen> with SingleTickerProviderStateM
|
||||
),
|
||||
],
|
||||
),
|
||||
)
|
||||
),
|
||||
const SliverGap(8),
|
||||
SliverToBoxAdapter(child: const Divider()),
|
||||
SliverList.builder(
|
||||
itemCount: _publishers?.length ?? 0,
|
||||
itemBuilder: (context, idx) {
|
||||
final ele = _publishers![idx];
|
||||
return ListTile(
|
||||
contentPadding: const EdgeInsets.symmetric(horizontal: 24),
|
||||
leading: AccountImage(
|
||||
content: ele.avatar,
|
||||
fallbackWidget: const Icon(Symbols.group, size: 24),
|
||||
),
|
||||
title: Text(ele.nick),
|
||||
subtitle: Text('@${ele.name}'),
|
||||
trailing: const Icon(Symbols.chevron_right),
|
||||
onTap: () {
|
||||
GoRouter.of(context).pushNamed(
|
||||
'postPublisher',
|
||||
pathParameters: {'name': ele.name},
|
||||
);
|
||||
},
|
||||
);
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
|
@ -31,7 +31,7 @@ class _PublisherScreenState extends State<PublisherScreen> {
|
||||
setState(() => _isBusy = true);
|
||||
|
||||
try {
|
||||
final resp = await sn.client.get('/cgi/co/publishers');
|
||||
final resp = await sn.client.get('/cgi/co/publishers/me');
|
||||
final List<SnPublisher> out = List<SnPublisher>.from(
|
||||
resp.data?.map((e) => SnPublisher.fromJson(e)) ?? []);
|
||||
|
||||
|
@ -1,6 +1,4 @@
|
||||
import 'dart:async';
|
||||
import 'dart:convert';
|
||||
import 'dart:developer';
|
||||
|
||||
import 'package:dio/dio.dart';
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
@ -69,9 +67,9 @@ class _ChatRoomScreenState extends State<ChatRoomScreen> {
|
||||
{},
|
||||
);
|
||||
_otherMember = _channel!.members?.cast<SnChannelMember?>().firstWhere(
|
||||
(ele) => ele?.accountId != ua.user?.id,
|
||||
orElse: () => null,
|
||||
);
|
||||
(ele) => ele?.accountId != ua.user?.id,
|
||||
orElse: () => null,
|
||||
);
|
||||
}
|
||||
} catch (err) {
|
||||
if (!mounted) return;
|
||||
@ -90,6 +88,8 @@ class _ChatRoomScreenState extends State<ChatRoomScreen> {
|
||||
'/cgi/im/channels/${_messageController.channel!.keyPath}/calls/ongoing',
|
||||
options: Options(
|
||||
validateStatus: (status) => status != null && status < 500,
|
||||
receiveTimeout: const Duration(seconds: 60),
|
||||
sendTimeout: const Duration(seconds: 60),
|
||||
),
|
||||
);
|
||||
if (resp.statusCode == 200) {
|
||||
@ -97,6 +97,7 @@ class _ChatRoomScreenState extends State<ChatRoomScreen> {
|
||||
}
|
||||
} catch (err) {
|
||||
if (!mounted) return;
|
||||
print((err as DioException).response?.data);
|
||||
context.showErrorDialog(err);
|
||||
} finally {
|
||||
setState(() => _isCalling = false);
|
||||
@ -108,17 +109,19 @@ class _ChatRoomScreenState extends State<ChatRoomScreen> {
|
||||
|
||||
try {
|
||||
final sn = context.read<SnNetworkProvider>();
|
||||
final resp = await sn.client.post(
|
||||
await sn.client.post(
|
||||
'/cgi/im/channels/${_messageController.channel!.keyPath}/calls',
|
||||
options: Options(
|
||||
sendTimeout: const Duration(seconds: 30),
|
||||
receiveTimeout: const Duration(seconds: 30),
|
||||
),
|
||||
);
|
||||
log(jsonDecode(resp.data));
|
||||
} catch (err) {
|
||||
if (!mounted) return;
|
||||
context.showErrorDialog(err);
|
||||
if (_ongoingCall == null) {
|
||||
// ignore the error because the call is already ongoing
|
||||
context.showErrorDialog(err);
|
||||
}
|
||||
} finally {
|
||||
setState(() => _isCalling = false);
|
||||
}
|
||||
@ -155,7 +158,7 @@ class _ChatRoomScreenState extends State<ChatRoomScreen> {
|
||||
GoRouter.of(context).pushNamed(
|
||||
'chatCallRoom',
|
||||
pathParameters: {
|
||||
'scope': _channel!.realm!.alias,
|
||||
'scope': _channel!.realm?.alias ?? 'global',
|
||||
'alias': _channel!.alias,
|
||||
},
|
||||
);
|
||||
|
@ -1,7 +1,10 @@
|
||||
import 'dart:io';
|
||||
import 'dart:math' as math;
|
||||
import 'dart:ui';
|
||||
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter_app_update/flutter_app_update.dart';
|
||||
import 'package:flutter_staggered_grid_view/flutter_staggered_grid_view.dart';
|
||||
import 'package:gap/gap.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
@ -10,6 +13,7 @@ import 'package:material_symbols_icons/symbols.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:styled_widget/styled_widget.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:surface/providers/config.dart';
|
||||
import 'package:surface/providers/post.dart';
|
||||
import 'package:surface/providers/sn_network.dart';
|
||||
import 'package:surface/providers/userinfo.dart';
|
||||
@ -19,6 +23,8 @@ import 'package:surface/widgets/app_bar_leading.dart';
|
||||
import 'package:surface/widgets/dialog.dart';
|
||||
import 'package:surface/widgets/post/post_item.dart';
|
||||
|
||||
import '../providers/widget.dart';
|
||||
|
||||
class HomeScreenDashEntry {
|
||||
final String name;
|
||||
final Widget child;
|
||||
@ -74,7 +80,8 @@ class _HomeScreenState extends State<HomeScreen> {
|
||||
child: Column(
|
||||
mainAxisAlignment: constraints.maxWidth > 640 ? MainAxisAlignment.center : MainAxisAlignment.start,
|
||||
children: [
|
||||
_HomeDashSpecialDayWidget().padding(top: 8, horizontal: 8),
|
||||
_HomeDashSpecialDayWidget().padding(bottom: 8, horizontal: 8),
|
||||
_HomeDashUpdateWidget(padding: const EdgeInsets.only(bottom: 8, left: 8, right: 8)),
|
||||
StaggeredGrid.extent(
|
||||
maxCrossAxisExtent: 280,
|
||||
mainAxisSpacing: 8,
|
||||
@ -98,6 +105,52 @@ class _HomeScreenState extends State<HomeScreen> {
|
||||
}
|
||||
}
|
||||
|
||||
class _HomeDashUpdateWidget extends StatelessWidget {
|
||||
final EdgeInsets? padding;
|
||||
|
||||
const _HomeDashUpdateWidget({super.key, this.padding});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final config = context.watch<ConfigProvider>();
|
||||
|
||||
return ListenableBuilder(
|
||||
listenable: config,
|
||||
builder: (context, _) {
|
||||
if (config.updatableVersion != null) {
|
||||
return Container(
|
||||
padding: padding,
|
||||
child: Card(
|
||||
child: ListTile(
|
||||
leading: Icon(Symbols.update),
|
||||
title: Text('updateAvailable').tr(),
|
||||
subtitle: Text(config.updatableVersion!),
|
||||
trailing: (kIsWeb || Platform.isWindows || Platform.isLinux)
|
||||
? null
|
||||
: IconButton(
|
||||
icon: const Icon(Symbols.arrow_right_alt),
|
||||
onPressed: () {
|
||||
final model = UpdateModel(
|
||||
'https://files.solsynth.dev/d/production01/solian/app-arm64-v8a-release.apk',
|
||||
'solian-app-release-${config.updatableVersion!}.apk',
|
||||
'ic_notification',
|
||||
'https://apps.apple.com/us/app/solian/id6499032345',
|
||||
);
|
||||
AzhonAppUpdate.update(model);
|
||||
context.showSnackbar('updateOngoing'.tr());
|
||||
},
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
return SizedBox.shrink();
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _HomeDashSpecialDayWidget extends StatelessWidget {
|
||||
const _HomeDashSpecialDayWidget({super.key});
|
||||
|
||||
@ -107,7 +160,9 @@ class _HomeDashSpecialDayWidget extends StatelessWidget {
|
||||
final today = DateTime.now();
|
||||
final birthday = ua.user?.profile?.birthday?.toLocal();
|
||||
final isBirthday = birthday != null && birthday.day == today.day && birthday.month == today.month;
|
||||
|
||||
return Column(
|
||||
spacing: 8,
|
||||
children: [
|
||||
if (isBirthday)
|
||||
Card(
|
||||
@ -116,6 +171,20 @@ class _HomeDashSpecialDayWidget extends StatelessWidget {
|
||||
title: Text('happyBirthday').tr(args: [ua.user?.nick ?? 'user']),
|
||||
),
|
||||
).padding(bottom: 8),
|
||||
if (today.month == 12 && today.day == 25)
|
||||
Card(
|
||||
child: ListTile(
|
||||
leading: Text('🎄').fontSize(24),
|
||||
title: Text('celebrateMerryXmas').tr(args: [ua.user?.nick ?? 'user']),
|
||||
),
|
||||
),
|
||||
if (today.month == 1 && today.day == 1)
|
||||
Card(
|
||||
child: ListTile(
|
||||
leading: Text('🎉').fontSize(24),
|
||||
title: Text('celebrateNewYear').tr(args: [ua.user?.nick ?? 'user']),
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
@ -140,8 +209,10 @@ class _HomeDashCheckInWidgetState extends State<_HomeDashCheckInWidget> {
|
||||
setState(() => _isBusy = true);
|
||||
try {
|
||||
final sn = context.read<SnNetworkProvider>();
|
||||
final home = context.read<HomeWidgetProvider>();
|
||||
final resp = await sn.client.get('/cgi/id/check-in/today');
|
||||
_todayRecord = SnCheckInRecord.fromJson(resp.data);
|
||||
home.saveWidgetData('pas_check_in_record', _todayRecord!.toJson());
|
||||
} finally {
|
||||
setState(() => _isBusy = false);
|
||||
}
|
||||
@ -151,8 +222,10 @@ class _HomeDashCheckInWidgetState extends State<_HomeDashCheckInWidget> {
|
||||
setState(() => _isBusy = true);
|
||||
try {
|
||||
final sn = context.read<SnNetworkProvider>();
|
||||
final home = context.read<HomeWidgetProvider>();
|
||||
final resp = await sn.client.post('/cgi/id/check-in');
|
||||
_todayRecord = SnCheckInRecord.fromJson(resp.data);
|
||||
home.saveWidgetData('pas_check_in_record', _todayRecord!.toJson());
|
||||
} catch (err) {
|
||||
if (!mounted) return;
|
||||
context.showErrorDialog(err);
|
||||
@ -171,7 +244,7 @@ class _HomeDashCheckInWidgetState extends State<_HomeDashCheckInWidget> {
|
||||
Text(
|
||||
prefix.tr(args: ['$prefix$pos'.tr()]),
|
||||
style: Theme.of(context).textTheme.titleMedium!.copyWith(fontWeight: FontWeight.bold),
|
||||
).tr(),
|
||||
),
|
||||
Text(
|
||||
'$prefix${pos}Description',
|
||||
style: Theme.of(context).textTheme.bodyMedium,
|
||||
@ -421,7 +494,9 @@ class _HomeDashRecommendationPostWidgetState extends State<_HomeDashRecommendati
|
||||
setState(() => _isBusy = true);
|
||||
try {
|
||||
final pt = context.read<SnPostContentProvider>();
|
||||
final home = context.read<HomeWidgetProvider>();
|
||||
_posts = await pt.listRecommendations();
|
||||
home.saveWidgetData('post_featured', _posts!.first.toJson());
|
||||
} catch (err) {
|
||||
if (!mounted) return;
|
||||
context.showErrorDialog(err);
|
||||
|
@ -84,12 +84,16 @@ class _PostDetailScreenState extends State<PostDetailScreen> {
|
||||
text: TextSpan(children: [
|
||||
TextSpan(
|
||||
text: _data?.body['title'] ?? 'postNoun'.tr(),
|
||||
style: Theme.of(context).textTheme.titleLarge!.copyWith(color: Colors.white),
|
||||
style: Theme.of(context).textTheme.titleLarge!.copyWith(
|
||||
color: Theme.of(context).appBarTheme.foregroundColor!,
|
||||
),
|
||||
),
|
||||
const TextSpan(text: '\n'),
|
||||
TextSpan(
|
||||
text: 'postDetail'.tr(),
|
||||
style: Theme.of(context).textTheme.bodySmall!.copyWith(color: Colors.white),
|
||||
style: Theme.of(context).textTheme.bodySmall!.copyWith(
|
||||
color: Theme.of(context).appBarTheme.foregroundColor!,
|
||||
),
|
||||
),
|
||||
]),
|
||||
)
|
||||
|
@ -23,11 +23,26 @@ import 'package:surface/widgets/post/post_meta_editor.dart';
|
||||
import 'package:surface/widgets/dialog.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
|
||||
class PostEditorExtraProps {
|
||||
final String? text;
|
||||
final String? title;
|
||||
final String? description;
|
||||
final List<PostWriteMedia>? attachments;
|
||||
|
||||
const PostEditorExtraProps({
|
||||
this.text,
|
||||
this.title,
|
||||
this.description,
|
||||
this.attachments,
|
||||
});
|
||||
}
|
||||
|
||||
class PostEditorScreen extends StatefulWidget {
|
||||
final String mode;
|
||||
final int? postEditId;
|
||||
final int? postReplyId;
|
||||
final int? postRepostId;
|
||||
final PostEditorExtraProps? extraProps;
|
||||
|
||||
const PostEditorScreen({
|
||||
super.key,
|
||||
@ -35,6 +50,7 @@ class PostEditorScreen extends StatefulWidget {
|
||||
required this.postEditId,
|
||||
required this.postReplyId,
|
||||
required this.postRepostId,
|
||||
this.extraProps,
|
||||
});
|
||||
|
||||
@override
|
||||
@ -55,7 +71,7 @@ class _PostEditorScreenState extends State<PostEditorScreen> {
|
||||
|
||||
try {
|
||||
final sn = context.read<SnNetworkProvider>();
|
||||
final resp = await sn.client.get('/cgi/co/publishers');
|
||||
final resp = await sn.client.get('/cgi/co/publishers/me');
|
||||
_publishers = List<SnPublisher>.from(
|
||||
resp.data?.map((e) => SnPublisher.fromJson(e)) ?? [],
|
||||
);
|
||||
@ -130,6 +146,12 @@ class _PostEditorScreenState extends State<PostEditorScreen> {
|
||||
replying: widget.postReplyId,
|
||||
reposting: widget.postRepostId,
|
||||
);
|
||||
if (widget.extraProps != null) {
|
||||
_writeController.contentController.text = widget.extraProps!.text ?? '';
|
||||
_writeController.titleController.text = widget.extraProps!.title ?? '';
|
||||
_writeController.descriptionController.text = widget.extraProps!.description ?? '';
|
||||
_writeController.addAttachments(widget.extraProps!.attachments ?? []);
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
@ -149,12 +171,16 @@ class _PostEditorScreenState extends State<PostEditorScreen> {
|
||||
text: TextSpan(children: [
|
||||
TextSpan(
|
||||
text: _writeController.title.isNotEmpty ? _writeController.title : 'untitled'.tr(),
|
||||
style: Theme.of(context).textTheme.titleLarge!.copyWith(color: Colors.white),
|
||||
style: Theme.of(context).textTheme.titleLarge!.copyWith(
|
||||
color: Theme.of(context).appBarTheme.foregroundColor!,
|
||||
),
|
||||
),
|
||||
const TextSpan(text: '\n'),
|
||||
TextSpan(
|
||||
text: PostWriteController.kTitleMap[widget.mode]!.tr(),
|
||||
style: Theme.of(context).textTheme.bodySmall!.copyWith(color: Colors.white),
|
||||
style: Theme.of(context).textTheme.bodySmall!.copyWith(
|
||||
color: Theme.of(context).appBarTheme.foregroundColor!,
|
||||
),
|
||||
),
|
||||
]),
|
||||
),
|
||||
|
@ -145,6 +145,7 @@ class _PostSearchScreenState extends State<PostSearchScreen> {
|
||||
padding: const WidgetStatePropertyAll(
|
||||
EdgeInsets.symmetric(horizontal: 24),
|
||||
),
|
||||
onTapOutside: (_) => FocusManager.instance.primaryFocus?.unfocus(),
|
||||
onChanged: (value) {
|
||||
_searchTerm = value;
|
||||
},
|
||||
|
@ -14,42 +14,54 @@ import 'package:surface/providers/sn_network.dart';
|
||||
import 'package:surface/providers/user_directory.dart';
|
||||
import 'package:surface/types/account.dart';
|
||||
import 'package:surface/types/post.dart';
|
||||
import 'package:surface/types/realm.dart';
|
||||
import 'package:surface/widgets/account/account_image.dart';
|
||||
import 'package:surface/widgets/dialog.dart';
|
||||
import 'package:surface/widgets/post/post_item.dart';
|
||||
import 'package:surface/widgets/universal_image.dart';
|
||||
import 'package:very_good_infinite_list/very_good_infinite_list.dart';
|
||||
|
||||
import '../../providers/relationship.dart';
|
||||
import '../abuse_report.dart';
|
||||
|
||||
class PostPublisherScreen extends StatefulWidget {
|
||||
final String name;
|
||||
|
||||
const PostPublisherScreen({super.key, required this.name});
|
||||
|
||||
@override
|
||||
State<PostPublisherScreen> createState() => _PostPublisherScreenState();
|
||||
}
|
||||
|
||||
class _PostPublisherScreenState extends State<PostPublisherScreen>
|
||||
with SingleTickerProviderStateMixin {
|
||||
class _PostPublisherScreenState extends State<PostPublisherScreen> with SingleTickerProviderStateMixin {
|
||||
late final ScrollController _scrollController = ScrollController();
|
||||
late final TabController _tabController =
|
||||
TabController(length: 3, vsync: this);
|
||||
late final TabController _tabController = TabController(length: 3, vsync: this);
|
||||
|
||||
SnPublisher? _publisher;
|
||||
SnAccount? _account;
|
||||
SnRelationship? _accountRelationship;
|
||||
SnRealm? _realm;
|
||||
|
||||
Future<void> _fetchPublisher() async {
|
||||
try {
|
||||
final sn = context.read<SnNetworkProvider>();
|
||||
final ud = context.read<UserDirectoryProvider>();
|
||||
final rel = context.read<SnRelationshipProvider>();
|
||||
final resp = await sn.client.get('/cgi/co/publishers/${widget.name}');
|
||||
if (!mounted) return;
|
||||
_publisher = SnPublisher.fromJson(resp.data);
|
||||
_account = await ud.getAccount(_publisher?.accountId);
|
||||
_accountRelationship = await rel.getRelationship(_account!.id);
|
||||
if (_publisher?.realmId != null && _publisher!.realmId != 0) {
|
||||
final resp = await sn.client.get('/cgi/id/realms/${_publisher!.realmId}');
|
||||
_realm = SnRealm.fromJson(resp.data);
|
||||
}
|
||||
} catch (err) {
|
||||
if (!mounted) return;
|
||||
context.showErrorDialog(err).then((_) {
|
||||
if (mounted) Navigator.pop(context);
|
||||
});
|
||||
rethrow;
|
||||
} finally {
|
||||
setState(() {});
|
||||
}
|
||||
@ -114,14 +126,12 @@ class _PostPublisherScreenState extends State<PostPublisherScreen>
|
||||
double _appBarBlur = 0.0;
|
||||
|
||||
late final _appBarWidth = MediaQuery.of(context).size.width;
|
||||
late final _appBarHeight =
|
||||
(_appBarWidth * kBannerAspectRatio).roundToDouble();
|
||||
late final _appBarHeight = (_appBarWidth * kBannerAspectRatio).roundToDouble();
|
||||
|
||||
void _updateAppBarBlur() {
|
||||
if (_scrollController.offset > _appBarHeight) return;
|
||||
setState(() {
|
||||
_appBarBlur =
|
||||
(_scrollController.offset / _appBarHeight * 10).clamp(0.0, 10.0);
|
||||
_appBarBlur = (_scrollController.offset / _appBarHeight * 10).clamp(0.0, 10.0);
|
||||
});
|
||||
}
|
||||
|
||||
@ -156,11 +166,72 @@ class _PostPublisherScreenState extends State<PostPublisherScreen>
|
||||
}
|
||||
}
|
||||
|
||||
bool _isWorking = false;
|
||||
|
||||
Future<void> _blockPublisher() async {
|
||||
if (_isWorking) return;
|
||||
|
||||
final confirm = await context.showConfirmDialog(
|
||||
'publisherBlockHint'.tr(args: ['@${_publisher?.name ?? 'unknown'.tr()}']),
|
||||
'publisherBlockHintDescription'.tr(),
|
||||
);
|
||||
if (!confirm) return;
|
||||
if (!mounted) return;
|
||||
|
||||
setState(() => _isWorking = true);
|
||||
|
||||
try {
|
||||
final sn = context.read<SnNetworkProvider>();
|
||||
await sn.client.post('/cgi/id/users/me/relations/block', data: {
|
||||
'related': _account!.name,
|
||||
});
|
||||
if (!mounted) return;
|
||||
context.showSnackbar('userBlocked'.tr(args: ['@${_account?.name ?? 'unknown'.tr()}']));
|
||||
} catch (err) {
|
||||
if (!mounted) return;
|
||||
context.showErrorDialog(err);
|
||||
} finally {
|
||||
setState(() => _isWorking = false);
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _unblockPublisher() async {
|
||||
if (_isWorking) return;
|
||||
|
||||
setState(() => _isWorking = true);
|
||||
|
||||
try {
|
||||
final rel = context.read<SnRelationshipProvider>();
|
||||
await rel.updateRelationship(_account!.id, 1, _accountRelationship?.permNodes ?? {});
|
||||
if (!mounted) return;
|
||||
context.showSnackbar('userUnblocked'.tr(args: ['@${_account?.name ?? 'unknown'.tr()}']));
|
||||
} catch (err) {
|
||||
if (!mounted) return;
|
||||
context.showErrorDialog(err);
|
||||
} finally {
|
||||
setState(() => _isWorking = false);
|
||||
}
|
||||
}
|
||||
|
||||
void _updateFetchType() {
|
||||
_posts.clear();
|
||||
_fetchPosts();
|
||||
}
|
||||
|
||||
void _showAbuseReportDialog() {
|
||||
showDialog(
|
||||
context: context,
|
||||
builder: (context) => AbuseReportDialog(
|
||||
resourceLocation: 'pub:${_publisher?.name}',
|
||||
),
|
||||
).then((value) {
|
||||
if (value == true && mounted) {
|
||||
_fetchPosts();
|
||||
context.showSnackbar('abuseReportSubmitted'.tr());
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
@ -215,21 +286,15 @@ class _PostPublisherScreenState extends State<PostPublisherScreen>
|
||||
text: TextSpan(children: [
|
||||
TextSpan(
|
||||
text: _publisher!.nick,
|
||||
style: Theme.of(context)
|
||||
.textTheme
|
||||
.titleLarge!
|
||||
.copyWith(
|
||||
color: Colors.white,
|
||||
style: Theme.of(context).textTheme.titleLarge!.copyWith(
|
||||
color: Theme.of(context).appBarTheme.foregroundColor!,
|
||||
shadows: labelShadows,
|
||||
),
|
||||
),
|
||||
const TextSpan(text: '\n'),
|
||||
TextSpan(
|
||||
text: '@${_publisher!.name}',
|
||||
style: Theme.of(context)
|
||||
.textTheme
|
||||
.bodySmall!
|
||||
.copyWith(
|
||||
style: Theme.of(context).textTheme.bodySmall!.copyWith(
|
||||
color: Colors.white,
|
||||
shadows: labelShadows,
|
||||
),
|
||||
@ -241,14 +306,19 @@ class _PostPublisherScreenState extends State<PostPublisherScreen>
|
||||
? Stack(
|
||||
fit: StackFit.expand,
|
||||
children: [
|
||||
UniversalImage(
|
||||
sn.getAttachmentUrl(_publisher!.banner),
|
||||
fit: BoxFit.cover,
|
||||
height: imageHeight,
|
||||
width: _appBarWidth,
|
||||
cacheHeight: imageHeight,
|
||||
cacheWidth: _appBarWidth,
|
||||
),
|
||||
if (_publisher!.banner.isNotEmpty)
|
||||
UniversalImage(
|
||||
sn.getAttachmentUrl(_publisher!.banner),
|
||||
fit: BoxFit.cover,
|
||||
height: imageHeight,
|
||||
width: _appBarWidth,
|
||||
cacheHeight: imageHeight,
|
||||
cacheWidth: _appBarWidth,
|
||||
)
|
||||
else
|
||||
Container(
|
||||
color: Theme.of(context).colorScheme.surfaceContainer,
|
||||
),
|
||||
Positioned(
|
||||
top: 0,
|
||||
left: 0,
|
||||
@ -288,14 +358,11 @@ class _PostPublisherScreenState extends State<PostPublisherScreen>
|
||||
const Gap(16),
|
||||
Expanded(
|
||||
child: Column(
|
||||
crossAxisAlignment:
|
||||
CrossAxisAlignment.start,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
_publisher!.nick,
|
||||
style: Theme.of(context)
|
||||
.textTheme
|
||||
.titleMedium,
|
||||
style: Theme.of(context).textTheme.titleMedium,
|
||||
).bold(),
|
||||
Text('@${_publisher!.name}').fontSize(13),
|
||||
],
|
||||
@ -306,9 +373,7 @@ class _PostPublisherScreenState extends State<PostPublisherScreen>
|
||||
style: ButtonStyle(
|
||||
elevation: WidgetStatePropertyAll(0),
|
||||
),
|
||||
onPressed: _isSubscribing
|
||||
? null
|
||||
: _toggleSubscription,
|
||||
onPressed: _isSubscribing ? null : _toggleSubscription,
|
||||
label: Text('subscribe').tr(),
|
||||
icon: const Icon(Symbols.add),
|
||||
)
|
||||
@ -317,17 +382,54 @@ class _PostPublisherScreenState extends State<PostPublisherScreen>
|
||||
style: ButtonStyle(
|
||||
elevation: WidgetStatePropertyAll(0),
|
||||
),
|
||||
onPressed: _isSubscribing
|
||||
? null
|
||||
: _toggleSubscription,
|
||||
onPressed: _isSubscribing ? null : _toggleSubscription,
|
||||
label: Text('unsubscribe').tr(),
|
||||
icon: const Icon(Symbols.remove),
|
||||
),
|
||||
PopupMenuButton(
|
||||
padding: EdgeInsets.zero,
|
||||
style: ButtonStyle(
|
||||
visualDensity: VisualDensity(horizontal: -4, vertical: -4),
|
||||
),
|
||||
itemBuilder: (BuildContext context) => [
|
||||
PopupMenuItem(
|
||||
child: Row(
|
||||
children: [
|
||||
const Icon(Symbols.flag),
|
||||
const Gap(16),
|
||||
Text('report').tr(),
|
||||
],
|
||||
),
|
||||
onTap: () => _showAbuseReportDialog(),
|
||||
),
|
||||
if (_accountRelationship?.status != 2)
|
||||
PopupMenuItem(
|
||||
onTap: _blockPublisher,
|
||||
child: Row(
|
||||
children: [
|
||||
const Icon(Symbols.block),
|
||||
const Gap(16),
|
||||
Text('friendBlock').tr(),
|
||||
],
|
||||
),
|
||||
)
|
||||
else
|
||||
PopupMenuItem(
|
||||
onTap: _unblockPublisher,
|
||||
child: Row(
|
||||
children: [
|
||||
const Icon(Symbols.block),
|
||||
const Gap(16),
|
||||
Text('friendUnblock').tr(),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
).padding(right: 8),
|
||||
),
|
||||
const Gap(12),
|
||||
Text(_publisher!.description)
|
||||
.padding(horizontal: 8),
|
||||
Text(_publisher!.description).padding(horizontal: 8),
|
||||
const Gap(12),
|
||||
Column(
|
||||
children: [
|
||||
@ -335,10 +437,8 @@ class _PostPublisherScreenState extends State<PostPublisherScreen>
|
||||
children: [
|
||||
const Icon(Symbols.calendar_add_on),
|
||||
const Gap(8),
|
||||
Text('publisherJoinedAt').tr(args: [
|
||||
DateFormat('y/M/d')
|
||||
.format(_publisher!.createdAt)
|
||||
]),
|
||||
Text('publisherJoinedAt')
|
||||
.tr(args: [DateFormat('y/M/d').format(_publisher!.createdAt)]),
|
||||
],
|
||||
),
|
||||
Row(
|
||||
@ -346,11 +446,30 @@ class _PostPublisherScreenState extends State<PostPublisherScreen>
|
||||
const Icon(Symbols.trending_up),
|
||||
const Gap(8),
|
||||
Text('publisherSocialPointTotal').plural(
|
||||
_publisher!.totalUpvote -
|
||||
_publisher!.totalDownvote,
|
||||
_publisher!.totalUpvote - _publisher!.totalDownvote,
|
||||
),
|
||||
],
|
||||
),
|
||||
if (_realm != null)
|
||||
Row(
|
||||
children: [
|
||||
const Icon(Symbols.group_work),
|
||||
const Gap(8),
|
||||
InkWell(
|
||||
child: Text('publisherAffiliatedBy').tr(args: [
|
||||
'@${_realm?.alias ?? 'unknown'}',
|
||||
]),
|
||||
onTap: () {
|
||||
GoRouter.of(context).pushNamed(
|
||||
'realmDetail',
|
||||
pathParameters: {'alias': _realm!.alias},
|
||||
);
|
||||
},
|
||||
),
|
||||
const Gap(8),
|
||||
AccountImage(content: _realm?.avatar, radius: 8),
|
||||
],
|
||||
),
|
||||
Row(
|
||||
children: [
|
||||
const Icon(Symbols.tools_wrench),
|
||||
@ -369,8 +488,7 @@ class _PostPublisherScreenState extends State<PostPublisherScreen>
|
||||
},
|
||||
),
|
||||
const Gap(8),
|
||||
AccountImage(
|
||||
content: _account?.avatar, radius: 8),
|
||||
AccountImage(content: _account?.avatar, radius: 8),
|
||||
],
|
||||
),
|
||||
],
|
||||
@ -447,6 +565,7 @@ class _PublisherPostList extends StatelessWidget {
|
||||
final void Function() fetchPosts;
|
||||
final void Function(int index, SnPost data) onChanged;
|
||||
final void Function() onDeleted;
|
||||
|
||||
const _PublisherPostList({
|
||||
super.key,
|
||||
required this.isBusy,
|
||||
|
@ -6,16 +6,15 @@ import 'package:material_symbols_icons/symbols.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:styled_widget/styled_widget.dart';
|
||||
import 'package:surface/providers/sn_network.dart';
|
||||
import 'package:surface/providers/userinfo.dart';
|
||||
import 'package:surface/types/realm.dart';
|
||||
import 'package:surface/widgets/account/account_image.dart';
|
||||
import 'package:surface/widgets/app_bar_leading.dart';
|
||||
import 'package:surface/widgets/dialog.dart';
|
||||
import 'package:surface/widgets/loading_indicator.dart';
|
||||
import 'package:surface/widgets/unauthorized_hint.dart';
|
||||
import 'package:surface/widgets/universal_image.dart';
|
||||
|
||||
import '../providers/userinfo.dart';
|
||||
import '../widgets/unauthorized_hint.dart';
|
||||
|
||||
class RealmScreen extends StatefulWidget {
|
||||
const RealmScreen({super.key});
|
||||
|
||||
@ -101,9 +100,7 @@ class _RealmScreenState extends State<RealmScreen> {
|
||||
title: Text('screenRealm').tr(),
|
||||
actions: [
|
||||
IconButton(
|
||||
icon: !_isCompactView
|
||||
? const Icon(Symbols.view_list)
|
||||
: const Icon(Symbols.view_module),
|
||||
icon: !_isCompactView ? const Icon(Symbols.view_list) : const Icon(Symbols.view_module),
|
||||
onPressed: () {
|
||||
setState(() => _isCompactView = !_isCompactView);
|
||||
},
|
||||
@ -129,8 +126,7 @@ class _RealmScreenState extends State<RealmScreen> {
|
||||
final realm = _realms![idx];
|
||||
if (_isCompactView) {
|
||||
return ListTile(
|
||||
contentPadding:
|
||||
const EdgeInsets.symmetric(horizontal: 16),
|
||||
contentPadding: const EdgeInsets.symmetric(horizontal: 16),
|
||||
leading: AccountImage(
|
||||
content: realm.avatar,
|
||||
fallbackWidget: const Icon(Symbols.group, size: 20),
|
||||
@ -201,9 +197,7 @@ class _RealmScreenState extends State<RealmScreen> {
|
||||
fit: StackFit.expand,
|
||||
children: [
|
||||
Container(
|
||||
color: Theme.of(context)
|
||||
.colorScheme
|
||||
.surfaceContainer,
|
||||
color: Theme.of(context).colorScheme.surfaceContainer,
|
||||
child: (realm.banner?.isEmpty ?? true)
|
||||
? const SizedBox.shrink()
|
||||
: AutoResizeUniversalImage(
|
||||
@ -217,8 +211,7 @@ class _RealmScreenState extends State<RealmScreen> {
|
||||
child: AccountImage(
|
||||
content: realm.avatar,
|
||||
radius: 24,
|
||||
fallbackWidget:
|
||||
const Icon(Symbols.group, size: 24),
|
||||
fallbackWidget: const Icon(Symbols.group, size: 24),
|
||||
),
|
||||
),
|
||||
],
|
||||
@ -228,10 +221,8 @@ class _RealmScreenState extends State<RealmScreen> {
|
||||
Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(realm.name).textStyle(
|
||||
Theme.of(context).textTheme.titleMedium!),
|
||||
Text(realm.description).textStyle(
|
||||
Theme.of(context).textTheme.bodySmall!),
|
||||
Text(realm.name).textStyle(Theme.of(context).textTheme.titleMedium!),
|
||||
Text(realm.description).textStyle(Theme.of(context).textTheme.bodySmall!),
|
||||
],
|
||||
).padding(horizontal: 24, bottom: 14),
|
||||
],
|
||||
|
@ -13,8 +13,11 @@ import 'package:surface/widgets/account/account_image.dart';
|
||||
import 'package:surface/widgets/dialog.dart';
|
||||
import 'package:very_good_infinite_list/very_good_infinite_list.dart';
|
||||
|
||||
import '../../types/post.dart';
|
||||
|
||||
class RealmDetailScreen extends StatefulWidget {
|
||||
final String alias;
|
||||
|
||||
const RealmDetailScreen({super.key, required this.alias});
|
||||
|
||||
@override
|
||||
@ -32,6 +35,24 @@ class _RealmDetailScreenState extends State<RealmDetailScreen> {
|
||||
} catch (err) {
|
||||
if (!mounted) return;
|
||||
context.showErrorDialog(err);
|
||||
rethrow;
|
||||
} finally {
|
||||
setState(() {});
|
||||
}
|
||||
}
|
||||
|
||||
List<SnPublisher>? _publishers;
|
||||
|
||||
Future<void> _fetchPublishers() async {
|
||||
try {
|
||||
final sn = context.read<SnNetworkProvider>();
|
||||
final resp = await sn.client.get('/cgi/co/publishers?realm=${widget.alias}');
|
||||
_publishers = List<SnPublisher>.from(
|
||||
resp.data?.map((e) => SnPublisher.fromJson(e)) ?? [],
|
||||
);
|
||||
} catch (err) {
|
||||
if (mounted) context.showErrorDialog(err);
|
||||
rethrow;
|
||||
} finally {
|
||||
setState(() {});
|
||||
}
|
||||
@ -40,7 +61,9 @@ class _RealmDetailScreenState extends State<RealmDetailScreen> {
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_fetchRealm();
|
||||
_fetchRealm().then((_) {
|
||||
_fetchPublishers();
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
@ -60,8 +83,7 @@ class _RealmDetailScreenState extends State<RealmDetailScreen> {
|
||||
// scroll view thinks it has not been scrolled.
|
||||
// This is not necessary if the "headerSliverBuilder" only builds
|
||||
// widgets that do not overlap the next sliver.
|
||||
handle:
|
||||
NestedScrollView.sliverOverlapAbsorberHandleFor(context),
|
||||
handle: NestedScrollView.sliverOverlapAbsorberHandleFor(context),
|
||||
sliver: SliverAppBar(
|
||||
title: Text(_realm?.name ?? 'loading'.tr()),
|
||||
bottom: TabBar(
|
||||
@ -77,7 +99,7 @@ class _RealmDetailScreenState extends State<RealmDetailScreen> {
|
||||
},
|
||||
body: TabBarView(
|
||||
children: [
|
||||
_RealmDetailHomeWidget(realm: _realm),
|
||||
_RealmDetailHomeWidget(realm: _realm, publishers: _publishers),
|
||||
_RealmMemberListWidget(realm: _realm),
|
||||
_RealmSettingsWidget(
|
||||
realm: _realm,
|
||||
@ -95,7 +117,9 @@ class _RealmDetailScreenState extends State<RealmDetailScreen> {
|
||||
|
||||
class _RealmDetailHomeWidget extends StatelessWidget {
|
||||
final SnRealm? realm;
|
||||
const _RealmDetailHomeWidget({super.key, required this.realm});
|
||||
final List<SnPublisher>? publishers;
|
||||
|
||||
const _RealmDetailHomeWidget({super.key, required this.realm, this.publishers});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
@ -118,6 +142,31 @@ class _RealmDetailHomeWidget extends StatelessWidget {
|
||||
).padding(horizontal: 24),
|
||||
const Gap(16),
|
||||
const Divider(),
|
||||
Expanded(
|
||||
child: ListView.builder(
|
||||
padding: EdgeInsets.zero,
|
||||
itemCount: publishers?.length ?? 0,
|
||||
itemBuilder: (context, idx) {
|
||||
final ele = publishers![idx];
|
||||
return ListTile(
|
||||
contentPadding: const EdgeInsets.symmetric(horizontal: 20),
|
||||
leading: AccountImage(
|
||||
content: ele.avatar,
|
||||
fallbackWidget: const Icon(Symbols.group, size: 24),
|
||||
),
|
||||
title: Text(ele.nick),
|
||||
subtitle: Text('@${ele.name}'),
|
||||
trailing: const Icon(Symbols.chevron_right),
|
||||
onTap: () {
|
||||
GoRouter.of(context).pushNamed(
|
||||
'postPublisher',
|
||||
pathParameters: {'name': ele.name},
|
||||
);
|
||||
},
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
@ -125,6 +174,7 @@ class _RealmDetailHomeWidget extends StatelessWidget {
|
||||
|
||||
class _RealmMemberListWidget extends StatefulWidget {
|
||||
final SnRealm? realm;
|
||||
|
||||
const _RealmMemberListWidget({super.key, this.realm});
|
||||
|
||||
@override
|
||||
@ -143,12 +193,10 @@ class _RealmMemberListWidgetState extends State<_RealmMemberListWidget> {
|
||||
try {
|
||||
final ud = context.read<UserDirectoryProvider>();
|
||||
final sn = context.read<SnNetworkProvider>();
|
||||
final resp = await sn.client.get(
|
||||
'/cgi/id/realms/${widget.realm!.alias}/members',
|
||||
queryParameters: {
|
||||
'take': 10,
|
||||
'offset': 0,
|
||||
});
|
||||
final resp = await sn.client.get('/cgi/id/realms/${widget.realm!.alias}/members', queryParameters: {
|
||||
'take': 10,
|
||||
'offset': 0,
|
||||
});
|
||||
|
||||
final out = List<SnRealmMember>.from(
|
||||
resp.data['data']?.map((e) => SnRealmMember.fromJson(e)) ?? [],
|
||||
@ -236,12 +284,10 @@ class _RealmMemberListWidgetState extends State<_RealmMemberListWidget> {
|
||||
fallbackWidget: const Icon(Symbols.group, size: 24),
|
||||
),
|
||||
title: Text(
|
||||
ud.getAccountFromCache(member.accountId)?.nick ??
|
||||
'unknown'.tr(),
|
||||
ud.getAccountFromCache(member.accountId)?.nick ?? 'unknown'.tr(),
|
||||
),
|
||||
subtitle: Text(
|
||||
ud.getAccountFromCache(member.accountId)?.name ??
|
||||
'unknown'.tr(),
|
||||
ud.getAccountFromCache(member.accountId)?.name ?? 'unknown'.tr(),
|
||||
),
|
||||
trailing: IconButton(
|
||||
icon: const Icon(Symbols.person_remove),
|
||||
@ -257,6 +303,7 @@ class _RealmMemberListWidgetState extends State<_RealmMemberListWidget> {
|
||||
|
||||
class _NewRealmMemberWidget extends StatefulWidget {
|
||||
final SnRealm realm;
|
||||
|
||||
const _NewRealmMemberWidget({super.key, required this.realm});
|
||||
|
||||
@override
|
||||
@ -321,8 +368,7 @@ class _NewRealmMemberWidgetState extends State<_NewRealmMemberWidget> {
|
||||
child: IconButton(
|
||||
onPressed: _isBusy ? null : () => _performAction(),
|
||||
icon: Icon(Symbols.send),
|
||||
visualDensity:
|
||||
const VisualDensity(horizontal: -4, vertical: -4),
|
||||
visualDensity: const VisualDensity(horizontal: -4, vertical: -4),
|
||||
padding: EdgeInsets.zero,
|
||||
),
|
||||
),
|
||||
@ -337,8 +383,8 @@ class _NewRealmMemberWidgetState extends State<_NewRealmMemberWidget> {
|
||||
class _RealmSettingsWidget extends StatefulWidget {
|
||||
final SnRealm? realm;
|
||||
final Function() onUpdate;
|
||||
const _RealmSettingsWidget(
|
||||
{super.key, required this.realm, required this.onUpdate});
|
||||
|
||||
const _RealmSettingsWidget({super.key, required this.realm, required this.onUpdate});
|
||||
|
||||
@override
|
||||
State<_RealmSettingsWidget> createState() => _RealmSettingsWidgetState();
|
||||
@ -382,6 +428,7 @@ class _RealmSettingsWidgetState extends State<_RealmSettingsWidget> {
|
||||
|
||||
return Column(
|
||||
children: [
|
||||
const Gap(16),
|
||||
ListTile(
|
||||
leading: const Icon(Symbols.edit),
|
||||
trailing: const Icon(Symbols.chevron_right),
|
||||
|
@ -1,10 +1,10 @@
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:collection/collection.dart';
|
||||
import 'package:dropdown_button2/dropdown_button2.dart';
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:gap/gap.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
import 'package:image_picker/image_picker.dart';
|
||||
import 'package:material_symbols_icons/symbols.dart';
|
||||
@ -12,6 +12,7 @@ import 'package:path_provider/path_provider.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:shared_preferences/shared_preferences.dart';
|
||||
import 'package:styled_widget/styled_widget.dart';
|
||||
import 'package:surface/providers/config.dart';
|
||||
import 'package:surface/providers/sn_network.dart';
|
||||
import 'package:surface/providers/theme.dart';
|
||||
import 'package:surface/theme.dart';
|
||||
@ -25,7 +26,7 @@ class SettingsScreen extends StatefulWidget {
|
||||
}
|
||||
|
||||
class _SettingsScreenState extends State<SettingsScreen> {
|
||||
SharedPreferences? _prefs;
|
||||
late final SharedPreferences _prefs;
|
||||
String _docBasepath = '/';
|
||||
|
||||
final TextEditingController _serverUrlController = TextEditingController();
|
||||
@ -39,12 +40,9 @@ class _SettingsScreenState extends State<SettingsScreen> {
|
||||
setState(() {});
|
||||
}
|
||||
});
|
||||
SharedPreferences.getInstance().then((prefs) {
|
||||
setState(() {
|
||||
_prefs = prefs;
|
||||
_serverUrlController.text = prefs.getString(kNetworkServerStoreKey) ?? kNetworkServerDefault;
|
||||
});
|
||||
});
|
||||
final config = context.read<ConfigProvider>();
|
||||
_prefs = config.prefs;
|
||||
_serverUrlController.text = config.serverUrl;
|
||||
}
|
||||
|
||||
@override
|
||||
@ -60,6 +58,7 @@ class _SettingsScreenState extends State<SettingsScreen> {
|
||||
return Scaffold(
|
||||
body: SingleChildScrollView(
|
||||
child: Column(
|
||||
spacing: 16,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Column(
|
||||
@ -78,7 +77,7 @@ class _SettingsScreenState extends State<SettingsScreen> {
|
||||
if (image == null) return;
|
||||
|
||||
await File(image.path).copy('$_docBasepath/app_background_image');
|
||||
_prefs?.setBool('has_background_image', true);
|
||||
_prefs.setBool('has_background_image', true);
|
||||
|
||||
setState(() {});
|
||||
},
|
||||
@ -99,29 +98,28 @@ class _SettingsScreenState extends State<SettingsScreen> {
|
||||
trailing: const Icon(Symbols.chevron_right),
|
||||
onTap: () {
|
||||
File('$_docBasepath/app_background_image').deleteSync();
|
||||
_prefs?.remove('has_background_image');
|
||||
_prefs.remove('has_background_image');
|
||||
setState(() {});
|
||||
},
|
||||
);
|
||||
}),
|
||||
if (_prefs != null)
|
||||
CheckboxListTile(
|
||||
title: Text('settingsThemeMaterial3').tr(),
|
||||
subtitle: Text('settingsThemeMaterial3Description').tr(),
|
||||
contentPadding: const EdgeInsets.only(left: 24, right: 17),
|
||||
secondary: const Icon(Symbols.new_releases),
|
||||
value: _prefs!.getBool(kMaterialYouToggleStoreKey) ?? false,
|
||||
onChanged: (value) {
|
||||
setState(() {
|
||||
_prefs!.setBool(
|
||||
kMaterialYouToggleStoreKey,
|
||||
value ?? false,
|
||||
);
|
||||
});
|
||||
final th = context.watch<ThemeProvider>();
|
||||
th.reloadTheme(useMaterial3: value ?? false);
|
||||
},
|
||||
),
|
||||
CheckboxListTile(
|
||||
title: Text('settingsThemeMaterial3').tr(),
|
||||
subtitle: Text('settingsThemeMaterial3Description').tr(),
|
||||
contentPadding: const EdgeInsets.only(left: 24, right: 17),
|
||||
secondary: const Icon(Symbols.new_releases),
|
||||
value: _prefs.getBool(kMaterialYouToggleStoreKey) ?? false,
|
||||
onChanged: (value) {
|
||||
setState(() {
|
||||
_prefs.setBool(
|
||||
kMaterialYouToggleStoreKey,
|
||||
value ?? false,
|
||||
);
|
||||
});
|
||||
final th = context.watch<ThemeProvider>();
|
||||
th.reloadTheme(useMaterial3: value ?? false);
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
Column(
|
||||
@ -139,7 +137,7 @@ class _SettingsScreenState extends State<SettingsScreen> {
|
||||
icon: const Icon(Symbols.save),
|
||||
onPressed: () {
|
||||
sn.setBaseUrl(_serverUrlController.text);
|
||||
_prefs?.setString(
|
||||
_prefs.setString(
|
||||
kNetworkServerStoreKey,
|
||||
_serverUrlController.text,
|
||||
);
|
||||
@ -182,7 +180,7 @@ class _SettingsScreenState extends State<SettingsScreen> {
|
||||
onChanged: (String? value) {
|
||||
if (value == null) return;
|
||||
_serverUrlController.text = value;
|
||||
_prefs?.setString(kNetworkServerStoreKey, value);
|
||||
_prefs.setString(kNetworkServerStoreKey, value);
|
||||
context.showSnackbar('settingsNetworkServerSaved'.tr());
|
||||
setState(() {});
|
||||
},
|
||||
@ -208,13 +206,56 @@ class _SettingsScreenState extends State<SettingsScreen> {
|
||||
trailing: const Icon(Symbols.chevron_right),
|
||||
onTap: () {
|
||||
_serverUrlController.text = kNetworkServerDefault;
|
||||
_prefs?.remove(kNetworkServerStoreKey);
|
||||
_prefs.remove(kNetworkServerStoreKey);
|
||||
context.showSnackbar('settingsNetworkServerSaved'.tr());
|
||||
setState(() {});
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text('settingsPerformance').bold().fontSize(17).tr().padding(horizontal: 20, bottom: 4),
|
||||
ListTile(
|
||||
title: Text('settingsImageQuality').tr(),
|
||||
subtitle: Text('settingsImageQualityDescription').tr(),
|
||||
contentPadding: const EdgeInsets.symmetric(horizontal: 24),
|
||||
leading: const Icon(Symbols.image),
|
||||
trailing: DropdownButtonHideUnderline(
|
||||
child: DropdownButton2<FilterQuality>(
|
||||
value: kImageQualityLevel.values.elementAtOrNull(_prefs.getInt('app_image_quality') ?? 3) ??
|
||||
FilterQuality.high,
|
||||
isExpanded: true,
|
||||
items: kImageQualityLevel.entries
|
||||
.map(
|
||||
(item) => DropdownMenuItem<FilterQuality>(
|
||||
value: item.value,
|
||||
child: Text(item.key).tr().fontSize(14),
|
||||
),
|
||||
)
|
||||
.toList(),
|
||||
onChanged: (FilterQuality? value) {
|
||||
if (value == null) return;
|
||||
_prefs.setInt('app_image_quality', kImageQualityLevel.values.toList().indexOf(value));
|
||||
setState(() {});
|
||||
},
|
||||
buttonStyleData: const ButtonStyleData(
|
||||
padding: EdgeInsets.symmetric(
|
||||
horizontal: 16,
|
||||
vertical: 5,
|
||||
),
|
||||
height: 40,
|
||||
width: 160,
|
||||
),
|
||||
menuItemStyleData: const MenuItemStyleData(
|
||||
height: 60,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
@ -231,7 +272,7 @@ class _SettingsScreenState extends State<SettingsScreen> {
|
||||
),
|
||||
],
|
||||
),
|
||||
].expand((ele) => [ele, const Gap(16)]).toList(),
|
||||
],
|
||||
).padding(vertical: 20),
|
||||
),
|
||||
);
|
||||
|
122
lib/screens/sharing.dart
Normal file
122
lib/screens/sharing.dart
Normal file
@ -0,0 +1,122 @@
|
||||
import 'dart:async';
|
||||
import 'dart:developer';
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:cross_file/cross_file.dart';
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:gap/gap.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
import 'package:receive_sharing_intent/receive_sharing_intent.dart';
|
||||
import 'package:surface/controllers/post_write_controller.dart';
|
||||
import 'package:surface/screens/post/post_editor.dart';
|
||||
|
||||
class AppSharingListener extends StatefulWidget {
|
||||
final Widget child;
|
||||
|
||||
const AppSharingListener({super.key, required this.child});
|
||||
|
||||
@override
|
||||
State<AppSharingListener> createState() => _AppSharingListenerState();
|
||||
}
|
||||
|
||||
class _AppSharingListenerState extends State<AppSharingListener> {
|
||||
late StreamSubscription _shareIntentSubscription;
|
||||
|
||||
void _gotoPost(Iterable<SharedMediaFile> value) {
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||
showDialog(
|
||||
context: context,
|
||||
builder: (context) => AlertDialog(
|
||||
title: Text('shareIntent').tr(),
|
||||
content: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text('shareIntentDescription').tr(),
|
||||
const Gap(8),
|
||||
Card(
|
||||
child: Column(
|
||||
children: [
|
||||
ListTile(
|
||||
contentPadding: const EdgeInsets.symmetric(horizontal: 24),
|
||||
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(8)),
|
||||
leading: Icon(Icons.post_add),
|
||||
trailing: const Icon(Icons.chevron_right),
|
||||
title: Text('shareIntentPostStory').tr(),
|
||||
onTap: () {
|
||||
GoRouter.of(context).pushNamed(
|
||||
'postEditor',
|
||||
pathParameters: {
|
||||
'mode': 'stories',
|
||||
},
|
||||
extra: PostEditorExtraProps(
|
||||
text: value
|
||||
.where((e) => [SharedMediaType.text, SharedMediaType.url].contains(e.type))
|
||||
.map((e) => e.path).join('\n'),
|
||||
attachments: value
|
||||
.where((e) => [SharedMediaType.video, SharedMediaType.file, SharedMediaType.image].contains(e.type))
|
||||
.map((e) => PostWriteMedia.fromFile(XFile(e.path))).toList(),
|
||||
),
|
||||
);
|
||||
Navigator.pop(context);
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
)
|
||||
],
|
||||
),
|
||||
actions: [
|
||||
TextButton(
|
||||
onPressed: () => Navigator.pop(context),
|
||||
child: Text('dialogDismiss').tr(),
|
||||
)
|
||||
],
|
||||
),
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
void _initialize() async {
|
||||
_shareIntentSubscription = ReceiveSharingIntent.instance.getMediaStream().listen((value) {
|
||||
if (value.isEmpty) return;
|
||||
if (mounted) {
|
||||
_gotoPost(value);
|
||||
}
|
||||
}, onError: (err) {
|
||||
log("[ShareIntent] Unable to subscribe: $err");
|
||||
});
|
||||
}
|
||||
|
||||
void _initialHandle() {
|
||||
ReceiveSharingIntent.instance.getInitialMedia().then((value) {
|
||||
if (value.isEmpty) return;
|
||||
if (mounted) {
|
||||
_gotoPost(value);
|
||||
}
|
||||
ReceiveSharingIntent.instance.reset();
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
if(!kIsWeb && (Platform.isAndroid || Platform.isIOS)) {
|
||||
_initialize();
|
||||
_initialHandle();
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_shareIntentSubscription.cancel();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return widget.child;
|
||||
}
|
||||
}
|
28
lib/types/link.dart
Normal file
28
lib/types/link.dart
Normal file
@ -0,0 +1,28 @@
|
||||
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||
|
||||
part 'link.g.dart';
|
||||
part 'link.freezed.dart';
|
||||
|
||||
@freezed
|
||||
class SnLinkMeta with _$SnLinkMeta {
|
||||
const SnLinkMeta._();
|
||||
|
||||
const factory SnLinkMeta({
|
||||
required int id,
|
||||
required DateTime createdAt,
|
||||
required DateTime updatedAt,
|
||||
required DateTime? deletedAt,
|
||||
required String entryId,
|
||||
required String? icon,
|
||||
required String url,
|
||||
required String? title,
|
||||
required String? image,
|
||||
required String? video,
|
||||
required String? audio,
|
||||
required String? description,
|
||||
required String? siteName,
|
||||
required String? type,
|
||||
}) = _SnLinkMeta;
|
||||
|
||||
factory SnLinkMeta.fromJson(Map<String, dynamic> json) => _$SnLinkMetaFromJson(json);
|
||||
}
|
450
lib/types/link.freezed.dart
Normal file
450
lib/types/link.freezed.dart
Normal file
@ -0,0 +1,450 @@
|
||||
// coverage:ignore-file
|
||||
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||
// ignore_for_file: type=lint
|
||||
// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark
|
||||
|
||||
part of 'link.dart';
|
||||
|
||||
// **************************************************************************
|
||||
// FreezedGenerator
|
||||
// **************************************************************************
|
||||
|
||||
T _$identity<T>(T value) => value;
|
||||
|
||||
final _privateConstructorUsedError = UnsupportedError(
|
||||
'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more information: https://github.com/rrousselGit/freezed#adding-getters-and-methods-to-our-models');
|
||||
|
||||
SnLinkMeta _$SnLinkMetaFromJson(Map<String, dynamic> json) {
|
||||
return _SnLinkMeta.fromJson(json);
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
mixin _$SnLinkMeta {
|
||||
int get id => throw _privateConstructorUsedError;
|
||||
DateTime get createdAt => throw _privateConstructorUsedError;
|
||||
DateTime get updatedAt => throw _privateConstructorUsedError;
|
||||
DateTime? get deletedAt => throw _privateConstructorUsedError;
|
||||
String get entryId => throw _privateConstructorUsedError;
|
||||
String? get icon => throw _privateConstructorUsedError;
|
||||
String get url => throw _privateConstructorUsedError;
|
||||
String? get title => throw _privateConstructorUsedError;
|
||||
String? get image => throw _privateConstructorUsedError;
|
||||
String? get video => throw _privateConstructorUsedError;
|
||||
String? get audio => throw _privateConstructorUsedError;
|
||||
String? get description => throw _privateConstructorUsedError;
|
||||
String? get siteName => throw _privateConstructorUsedError;
|
||||
String? get type => throw _privateConstructorUsedError;
|
||||
|
||||
/// Serializes this SnLinkMeta to a JSON map.
|
||||
Map<String, dynamic> toJson() => throw _privateConstructorUsedError;
|
||||
|
||||
/// Create a copy of SnLinkMeta
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||
$SnLinkMetaCopyWith<SnLinkMeta> get copyWith =>
|
||||
throw _privateConstructorUsedError;
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
abstract class $SnLinkMetaCopyWith<$Res> {
|
||||
factory $SnLinkMetaCopyWith(
|
||||
SnLinkMeta value, $Res Function(SnLinkMeta) then) =
|
||||
_$SnLinkMetaCopyWithImpl<$Res, SnLinkMeta>;
|
||||
@useResult
|
||||
$Res call(
|
||||
{int id,
|
||||
DateTime createdAt,
|
||||
DateTime updatedAt,
|
||||
DateTime? deletedAt,
|
||||
String entryId,
|
||||
String? icon,
|
||||
String url,
|
||||
String? title,
|
||||
String? image,
|
||||
String? video,
|
||||
String? audio,
|
||||
String? description,
|
||||
String? siteName,
|
||||
String? type});
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
class _$SnLinkMetaCopyWithImpl<$Res, $Val extends SnLinkMeta>
|
||||
implements $SnLinkMetaCopyWith<$Res> {
|
||||
_$SnLinkMetaCopyWithImpl(this._value, this._then);
|
||||
|
||||
// ignore: unused_field
|
||||
final $Val _value;
|
||||
// ignore: unused_field
|
||||
final $Res Function($Val) _then;
|
||||
|
||||
/// Create a copy of SnLinkMeta
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@pragma('vm:prefer-inline')
|
||||
@override
|
||||
$Res call({
|
||||
Object? id = null,
|
||||
Object? createdAt = null,
|
||||
Object? updatedAt = null,
|
||||
Object? deletedAt = freezed,
|
||||
Object? entryId = null,
|
||||
Object? icon = freezed,
|
||||
Object? url = null,
|
||||
Object? title = freezed,
|
||||
Object? image = freezed,
|
||||
Object? video = freezed,
|
||||
Object? audio = freezed,
|
||||
Object? description = freezed,
|
||||
Object? siteName = freezed,
|
||||
Object? type = freezed,
|
||||
}) {
|
||||
return _then(_value.copyWith(
|
||||
id: null == id
|
||||
? _value.id
|
||||
: id // ignore: cast_nullable_to_non_nullable
|
||||
as int,
|
||||
createdAt: null == createdAt
|
||||
? _value.createdAt
|
||||
: createdAt // ignore: cast_nullable_to_non_nullable
|
||||
as DateTime,
|
||||
updatedAt: null == updatedAt
|
||||
? _value.updatedAt
|
||||
: updatedAt // ignore: cast_nullable_to_non_nullable
|
||||
as DateTime,
|
||||
deletedAt: freezed == deletedAt
|
||||
? _value.deletedAt
|
||||
: deletedAt // ignore: cast_nullable_to_non_nullable
|
||||
as DateTime?,
|
||||
entryId: null == entryId
|
||||
? _value.entryId
|
||||
: entryId // ignore: cast_nullable_to_non_nullable
|
||||
as String,
|
||||
icon: freezed == icon
|
||||
? _value.icon
|
||||
: icon // ignore: cast_nullable_to_non_nullable
|
||||
as String?,
|
||||
url: null == url
|
||||
? _value.url
|
||||
: url // ignore: cast_nullable_to_non_nullable
|
||||
as String,
|
||||
title: freezed == title
|
||||
? _value.title
|
||||
: title // ignore: cast_nullable_to_non_nullable
|
||||
as String?,
|
||||
image: freezed == image
|
||||
? _value.image
|
||||
: image // ignore: cast_nullable_to_non_nullable
|
||||
as String?,
|
||||
video: freezed == video
|
||||
? _value.video
|
||||
: video // ignore: cast_nullable_to_non_nullable
|
||||
as String?,
|
||||
audio: freezed == audio
|
||||
? _value.audio
|
||||
: audio // ignore: cast_nullable_to_non_nullable
|
||||
as String?,
|
||||
description: freezed == description
|
||||
? _value.description
|
||||
: description // ignore: cast_nullable_to_non_nullable
|
||||
as String?,
|
||||
siteName: freezed == siteName
|
||||
? _value.siteName
|
||||
: siteName // ignore: cast_nullable_to_non_nullable
|
||||
as String?,
|
||||
type: freezed == type
|
||||
? _value.type
|
||||
: type // ignore: cast_nullable_to_non_nullable
|
||||
as String?,
|
||||
) as $Val);
|
||||
}
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
abstract class _$$SnLinkMetaImplCopyWith<$Res>
|
||||
implements $SnLinkMetaCopyWith<$Res> {
|
||||
factory _$$SnLinkMetaImplCopyWith(
|
||||
_$SnLinkMetaImpl value, $Res Function(_$SnLinkMetaImpl) then) =
|
||||
__$$SnLinkMetaImplCopyWithImpl<$Res>;
|
||||
@override
|
||||
@useResult
|
||||
$Res call(
|
||||
{int id,
|
||||
DateTime createdAt,
|
||||
DateTime updatedAt,
|
||||
DateTime? deletedAt,
|
||||
String entryId,
|
||||
String? icon,
|
||||
String url,
|
||||
String? title,
|
||||
String? image,
|
||||
String? video,
|
||||
String? audio,
|
||||
String? description,
|
||||
String? siteName,
|
||||
String? type});
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
class __$$SnLinkMetaImplCopyWithImpl<$Res>
|
||||
extends _$SnLinkMetaCopyWithImpl<$Res, _$SnLinkMetaImpl>
|
||||
implements _$$SnLinkMetaImplCopyWith<$Res> {
|
||||
__$$SnLinkMetaImplCopyWithImpl(
|
||||
_$SnLinkMetaImpl _value, $Res Function(_$SnLinkMetaImpl) _then)
|
||||
: super(_value, _then);
|
||||
|
||||
/// Create a copy of SnLinkMeta
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@pragma('vm:prefer-inline')
|
||||
@override
|
||||
$Res call({
|
||||
Object? id = null,
|
||||
Object? createdAt = null,
|
||||
Object? updatedAt = null,
|
||||
Object? deletedAt = freezed,
|
||||
Object? entryId = null,
|
||||
Object? icon = freezed,
|
||||
Object? url = null,
|
||||
Object? title = freezed,
|
||||
Object? image = freezed,
|
||||
Object? video = freezed,
|
||||
Object? audio = freezed,
|
||||
Object? description = freezed,
|
||||
Object? siteName = freezed,
|
||||
Object? type = freezed,
|
||||
}) {
|
||||
return _then(_$SnLinkMetaImpl(
|
||||
id: null == id
|
||||
? _value.id
|
||||
: id // ignore: cast_nullable_to_non_nullable
|
||||
as int,
|
||||
createdAt: null == createdAt
|
||||
? _value.createdAt
|
||||
: createdAt // ignore: cast_nullable_to_non_nullable
|
||||
as DateTime,
|
||||
updatedAt: null == updatedAt
|
||||
? _value.updatedAt
|
||||
: updatedAt // ignore: cast_nullable_to_non_nullable
|
||||
as DateTime,
|
||||
deletedAt: freezed == deletedAt
|
||||
? _value.deletedAt
|
||||
: deletedAt // ignore: cast_nullable_to_non_nullable
|
||||
as DateTime?,
|
||||
entryId: null == entryId
|
||||
? _value.entryId
|
||||
: entryId // ignore: cast_nullable_to_non_nullable
|
||||
as String,
|
||||
icon: freezed == icon
|
||||
? _value.icon
|
||||
: icon // ignore: cast_nullable_to_non_nullable
|
||||
as String?,
|
||||
url: null == url
|
||||
? _value.url
|
||||
: url // ignore: cast_nullable_to_non_nullable
|
||||
as String,
|
||||
title: freezed == title
|
||||
? _value.title
|
||||
: title // ignore: cast_nullable_to_non_nullable
|
||||
as String?,
|
||||
image: freezed == image
|
||||
? _value.image
|
||||
: image // ignore: cast_nullable_to_non_nullable
|
||||
as String?,
|
||||
video: freezed == video
|
||||
? _value.video
|
||||
: video // ignore: cast_nullable_to_non_nullable
|
||||
as String?,
|
||||
audio: freezed == audio
|
||||
? _value.audio
|
||||
: audio // ignore: cast_nullable_to_non_nullable
|
||||
as String?,
|
||||
description: freezed == description
|
||||
? _value.description
|
||||
: description // ignore: cast_nullable_to_non_nullable
|
||||
as String?,
|
||||
siteName: freezed == siteName
|
||||
? _value.siteName
|
||||
: siteName // ignore: cast_nullable_to_non_nullable
|
||||
as String?,
|
||||
type: freezed == type
|
||||
? _value.type
|
||||
: type // ignore: cast_nullable_to_non_nullable
|
||||
as String?,
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
@JsonSerializable()
|
||||
class _$SnLinkMetaImpl extends _SnLinkMeta {
|
||||
const _$SnLinkMetaImpl(
|
||||
{required this.id,
|
||||
required this.createdAt,
|
||||
required this.updatedAt,
|
||||
required this.deletedAt,
|
||||
required this.entryId,
|
||||
required this.icon,
|
||||
required this.url,
|
||||
required this.title,
|
||||
required this.image,
|
||||
required this.video,
|
||||
required this.audio,
|
||||
required this.description,
|
||||
required this.siteName,
|
||||
required this.type})
|
||||
: super._();
|
||||
|
||||
factory _$SnLinkMetaImpl.fromJson(Map<String, dynamic> json) =>
|
||||
_$$SnLinkMetaImplFromJson(json);
|
||||
|
||||
@override
|
||||
final int id;
|
||||
@override
|
||||
final DateTime createdAt;
|
||||
@override
|
||||
final DateTime updatedAt;
|
||||
@override
|
||||
final DateTime? deletedAt;
|
||||
@override
|
||||
final String entryId;
|
||||
@override
|
||||
final String? icon;
|
||||
@override
|
||||
final String url;
|
||||
@override
|
||||
final String? title;
|
||||
@override
|
||||
final String? image;
|
||||
@override
|
||||
final String? video;
|
||||
@override
|
||||
final String? audio;
|
||||
@override
|
||||
final String? description;
|
||||
@override
|
||||
final String? siteName;
|
||||
@override
|
||||
final String? type;
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return 'SnLinkMeta(id: $id, createdAt: $createdAt, updatedAt: $updatedAt, deletedAt: $deletedAt, entryId: $entryId, icon: $icon, url: $url, title: $title, image: $image, video: $video, audio: $audio, description: $description, siteName: $siteName, type: $type)';
|
||||
}
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) {
|
||||
return identical(this, other) ||
|
||||
(other.runtimeType == runtimeType &&
|
||||
other is _$SnLinkMetaImpl &&
|
||||
(identical(other.id, id) || other.id == id) &&
|
||||
(identical(other.createdAt, createdAt) ||
|
||||
other.createdAt == createdAt) &&
|
||||
(identical(other.updatedAt, updatedAt) ||
|
||||
other.updatedAt == updatedAt) &&
|
||||
(identical(other.deletedAt, deletedAt) ||
|
||||
other.deletedAt == deletedAt) &&
|
||||
(identical(other.entryId, entryId) || other.entryId == entryId) &&
|
||||
(identical(other.icon, icon) || other.icon == icon) &&
|
||||
(identical(other.url, url) || other.url == url) &&
|
||||
(identical(other.title, title) || other.title == title) &&
|
||||
(identical(other.image, image) || other.image == image) &&
|
||||
(identical(other.video, video) || other.video == video) &&
|
||||
(identical(other.audio, audio) || other.audio == audio) &&
|
||||
(identical(other.description, description) ||
|
||||
other.description == description) &&
|
||||
(identical(other.siteName, siteName) ||
|
||||
other.siteName == siteName) &&
|
||||
(identical(other.type, type) || other.type == type));
|
||||
}
|
||||
|
||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||
@override
|
||||
int get hashCode => Object.hash(
|
||||
runtimeType,
|
||||
id,
|
||||
createdAt,
|
||||
updatedAt,
|
||||
deletedAt,
|
||||
entryId,
|
||||
icon,
|
||||
url,
|
||||
title,
|
||||
image,
|
||||
video,
|
||||
audio,
|
||||
description,
|
||||
siteName,
|
||||
type);
|
||||
|
||||
/// Create a copy of SnLinkMeta
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||
@override
|
||||
@pragma('vm:prefer-inline')
|
||||
_$$SnLinkMetaImplCopyWith<_$SnLinkMetaImpl> get copyWith =>
|
||||
__$$SnLinkMetaImplCopyWithImpl<_$SnLinkMetaImpl>(this, _$identity);
|
||||
|
||||
@override
|
||||
Map<String, dynamic> toJson() {
|
||||
return _$$SnLinkMetaImplToJson(
|
||||
this,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
abstract class _SnLinkMeta extends SnLinkMeta {
|
||||
const factory _SnLinkMeta(
|
||||
{required final int id,
|
||||
required final DateTime createdAt,
|
||||
required final DateTime updatedAt,
|
||||
required final DateTime? deletedAt,
|
||||
required final String entryId,
|
||||
required final String? icon,
|
||||
required final String url,
|
||||
required final String? title,
|
||||
required final String? image,
|
||||
required final String? video,
|
||||
required final String? audio,
|
||||
required final String? description,
|
||||
required final String? siteName,
|
||||
required final String? type}) = _$SnLinkMetaImpl;
|
||||
const _SnLinkMeta._() : super._();
|
||||
|
||||
factory _SnLinkMeta.fromJson(Map<String, dynamic> json) =
|
||||
_$SnLinkMetaImpl.fromJson;
|
||||
|
||||
@override
|
||||
int get id;
|
||||
@override
|
||||
DateTime get createdAt;
|
||||
@override
|
||||
DateTime get updatedAt;
|
||||
@override
|
||||
DateTime? get deletedAt;
|
||||
@override
|
||||
String get entryId;
|
||||
@override
|
||||
String? get icon;
|
||||
@override
|
||||
String get url;
|
||||
@override
|
||||
String? get title;
|
||||
@override
|
||||
String? get image;
|
||||
@override
|
||||
String? get video;
|
||||
@override
|
||||
String? get audio;
|
||||
@override
|
||||
String? get description;
|
||||
@override
|
||||
String? get siteName;
|
||||
@override
|
||||
String? get type;
|
||||
|
||||
/// Create a copy of SnLinkMeta
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@override
|
||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||
_$$SnLinkMetaImplCopyWith<_$SnLinkMetaImpl> get copyWith =>
|
||||
throw _privateConstructorUsedError;
|
||||
}
|
45
lib/types/link.g.dart
Normal file
45
lib/types/link.g.dart
Normal file
@ -0,0 +1,45 @@
|
||||
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||
|
||||
part of 'link.dart';
|
||||
|
||||
// **************************************************************************
|
||||
// JsonSerializableGenerator
|
||||
// **************************************************************************
|
||||
|
||||
_$SnLinkMetaImpl _$$SnLinkMetaImplFromJson(Map<String, dynamic> json) =>
|
||||
_$SnLinkMetaImpl(
|
||||
id: (json['id'] as num).toInt(),
|
||||
createdAt: DateTime.parse(json['created_at'] as String),
|
||||
updatedAt: DateTime.parse(json['updated_at'] as String),
|
||||
deletedAt: json['deleted_at'] == null
|
||||
? null
|
||||
: DateTime.parse(json['deleted_at'] as String),
|
||||
entryId: json['entry_id'] as String,
|
||||
icon: json['icon'] as String?,
|
||||
url: json['url'] as String,
|
||||
title: json['title'] as String?,
|
||||
image: json['image'] as String?,
|
||||
video: json['video'] as String?,
|
||||
audio: json['audio'] as String?,
|
||||
description: json['description'] as String?,
|
||||
siteName: json['site_name'] as String?,
|
||||
type: json['type'] as String?,
|
||||
);
|
||||
|
||||
Map<String, dynamic> _$$SnLinkMetaImplToJson(_$SnLinkMetaImpl instance) =>
|
||||
<String, dynamic>{
|
||||
'id': instance.id,
|
||||
'created_at': instance.createdAt.toIso8601String(),
|
||||
'updated_at': instance.updatedAt.toIso8601String(),
|
||||
'deleted_at': instance.deletedAt?.toIso8601String(),
|
||||
'entry_id': instance.entryId,
|
||||
'icon': instance.icon,
|
||||
'url': instance.url,
|
||||
'title': instance.title,
|
||||
'image': instance.image,
|
||||
'video': instance.video,
|
||||
'audio': instance.audio,
|
||||
'description': instance.description,
|
||||
'site_name': instance.siteName,
|
||||
'type': instance.type,
|
||||
};
|
@ -1,5 +1,6 @@
|
||||
import 'dart:math' as math;
|
||||
|
||||
import 'package:collection/collection.dart';
|
||||
import 'package:dismissible_page/dismissible_page.dart';
|
||||
import 'package:flutter/gestures.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
@ -14,19 +15,21 @@ class AttachmentList extends StatefulWidget {
|
||||
final List<SnAttachment?> data;
|
||||
final bool bordered;
|
||||
final bool noGrow;
|
||||
final bool isFlatted;
|
||||
final double? maxHeight;
|
||||
final EdgeInsets? listPadding;
|
||||
|
||||
const AttachmentList({
|
||||
super.key,
|
||||
required this.data,
|
||||
this.bordered = false,
|
||||
this.noGrow = false,
|
||||
this.isFlatted = false,
|
||||
this.maxHeight,
|
||||
this.listPadding,
|
||||
});
|
||||
|
||||
static const BorderRadius kDefaultRadius =
|
||||
BorderRadius.all(Radius.circular(8));
|
||||
static const BorderRadius kDefaultRadius = BorderRadius.all(Radius.circular(8));
|
||||
|
||||
@override
|
||||
State<AttachmentList> createState() => _AttachmentListState();
|
||||
@ -44,9 +47,8 @@ class _AttachmentListState extends State<AttachmentList> {
|
||||
Widget build(BuildContext context) {
|
||||
return LayoutBuilder(
|
||||
builder: (context, layoutConstraints) {
|
||||
final borderSide = widget.bordered
|
||||
? BorderSide(width: 1, color: Theme.of(context).dividerColor)
|
||||
: BorderSide.none;
|
||||
final borderSide =
|
||||
widget.bordered ? BorderSide(width: 1, color: Theme.of(context).dividerColor) : BorderSide.none;
|
||||
final backgroundColor = Theme.of(context).colorScheme.surfaceContainer;
|
||||
final constraints = BoxConstraints(
|
||||
minWidth: 80,
|
||||
@ -56,14 +58,13 @@ class _AttachmentListState extends State<AttachmentList> {
|
||||
|
||||
if (widget.data.isEmpty) return const SizedBox.shrink();
|
||||
if (widget.data.length == 1) {
|
||||
final singleAspectRatio =
|
||||
widget.data[0]?.metadata['ratio']?.toDouble() ??
|
||||
switch (widget.data[0]?.mimetype.split('/').firstOrNull) {
|
||||
'audio' => 16 / 9,
|
||||
'video' => 16 / 9,
|
||||
_ => 1,
|
||||
}
|
||||
.toDouble();
|
||||
final singleAspectRatio = widget.data[0]?.metadata['ratio']?.toDouble() ??
|
||||
switch (widget.data[0]?.mimetype.split('/').firstOrNull) {
|
||||
'audio' => 16 / 9,
|
||||
'video' => 16 / 9,
|
||||
_ => 1,
|
||||
}
|
||||
.toDouble();
|
||||
|
||||
return Container(
|
||||
constraints: ResponsiveBreakpoints.of(context).largerThan(MOBILE)
|
||||
@ -79,8 +80,7 @@ class _AttachmentListState extends State<AttachmentList> {
|
||||
child: GestureDetector(
|
||||
child: Builder(
|
||||
builder: (context) {
|
||||
if (ResponsiveBreakpoints.of(context).largerThan(MOBILE) ||
|
||||
widget.noGrow) {
|
||||
if (ResponsiveBreakpoints.of(context).largerThan(MOBILE) || widget.noGrow) {
|
||||
return Padding(
|
||||
// Single child list-like displaying
|
||||
padding: widget.listPadding ?? EdgeInsets.zero,
|
||||
@ -129,8 +129,39 @@ class _AttachmentListState extends State<AttachmentList> {
|
||||
);
|
||||
}
|
||||
|
||||
if (widget.isFlatted) {
|
||||
return Wrap(
|
||||
spacing: 4,
|
||||
runSpacing: 4,
|
||||
children: widget.data
|
||||
.mapIndexed(
|
||||
(idx, ele) => AspectRatio(
|
||||
aspectRatio: (ele?.metadata['ratio'] ?? 1).toDouble(),
|
||||
child: Container(
|
||||
decoration: BoxDecoration(
|
||||
color: backgroundColor,
|
||||
border: Border(
|
||||
top: borderSide,
|
||||
bottom: borderSide,
|
||||
),
|
||||
borderRadius: AttachmentList.kDefaultRadius,
|
||||
),
|
||||
child: ClipRRect(
|
||||
borderRadius: AttachmentList.kDefaultRadius,
|
||||
child: AttachmentItem(
|
||||
data: ele,
|
||||
heroTag: heroTags[idx],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
)
|
||||
.toList(),
|
||||
);
|
||||
}
|
||||
|
||||
return AspectRatio(
|
||||
aspectRatio: widget.data.firstOrNull?.metadata['ratio'] ?? 1,
|
||||
aspectRatio: (widget.data.firstOrNull?.metadata['ratio'] ?? 1).toDouble(),
|
||||
child: Container(
|
||||
constraints: BoxConstraints(maxHeight: constraints.maxHeight),
|
||||
child: ScrollConfiguration(
|
||||
@ -142,14 +173,12 @@ class _AttachmentListState extends State<AttachmentList> {
|
||||
return Container(
|
||||
constraints: constraints,
|
||||
child: AspectRatio(
|
||||
aspectRatio: widget.data[idx]?.metadata['ratio'] ?? 1,
|
||||
aspectRatio: (widget.data[idx]?.metadata['ratio'] ?? 1).toDouble(),
|
||||
child: GestureDetector(
|
||||
onTap: () {
|
||||
context.pushTransparentRoute(
|
||||
AttachmentZoomView(
|
||||
data: widget.data
|
||||
.where((ele) => ele != null)
|
||||
.cast(),
|
||||
data: widget.data.where((ele) => ele != null).cast(),
|
||||
initialIndex: idx,
|
||||
heroTags: heroTags,
|
||||
),
|
||||
|
@ -3,6 +3,7 @@ import 'dart:io';
|
||||
import 'package:dio/dio.dart';
|
||||
import 'package:dismissible_page/dismissible_page.dart';
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:file_saver/file_saver.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:gal/gal.dart';
|
||||
@ -64,10 +65,6 @@ class _AttachmentZoomViewState extends State<AttachmentZoomView> {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!await Gal.hasAccess(toAlbum: true)) {
|
||||
if (!await Gal.requestAccess(toAlbum: true)) return;
|
||||
}
|
||||
|
||||
setState(() => _isDownloading = true);
|
||||
|
||||
var extName = extension(item.name);
|
||||
@ -83,7 +80,17 @@ class _AttachmentZoomViewState extends State<AttachmentZoomView> {
|
||||
|
||||
bool isSuccess = false;
|
||||
try {
|
||||
await Gal.putImage(imagePath, album: 'Solar Network');
|
||||
if (!kIsWeb && (Platform.isAndroid || Platform.isIOS)) {
|
||||
if (!await Gal.hasAccess(toAlbum: true)) {
|
||||
if (!await Gal.requestAccess(toAlbum: true)) return;
|
||||
}
|
||||
await Gal.putImage(imagePath, album: 'Solar Network');
|
||||
} else {
|
||||
await FileSaver.instance.saveFile(
|
||||
name: item.name,
|
||||
file: File(imagePath),
|
||||
);
|
||||
}
|
||||
setState(() {
|
||||
isSuccess = true;
|
||||
_isDownloading = false;
|
||||
@ -96,11 +103,13 @@ class _AttachmentZoomViewState extends State<AttachmentZoomView> {
|
||||
|
||||
if (!mounted) return;
|
||||
context.showSnackbar(
|
||||
'attachmentSaved'.tr(),
|
||||
action: SnackBarAction(
|
||||
label: 'openInAlbum'.tr(),
|
||||
onPressed: () async => Gal.open(),
|
||||
),
|
||||
(!kIsWeb && (Platform.isIOS || Platform.isAndroid)) ? 'attachmentSaved'.tr() : 'attachmentSavedDesktop'.tr(),
|
||||
action: (!kIsWeb && (Platform.isIOS || Platform.isAndroid))
|
||||
? SnackBarAction(
|
||||
label: 'openInAlbum'.tr(),
|
||||
onPressed: () async => Gal.open(),
|
||||
)
|
||||
: null,
|
||||
);
|
||||
}
|
||||
|
||||
@ -252,6 +261,7 @@ class _AttachmentZoomViewState extends State<AttachmentZoomView> {
|
||||
).padding(right: 8),
|
||||
),
|
||||
InkWell(
|
||||
borderRadius: const BorderRadius.all(Radius.circular(16)),
|
||||
onTap: _isDownloading
|
||||
? null
|
||||
: () => _saveToAlbum(widget.data.length > 1 ? _pageController.page?.round() ?? 0 : 0),
|
||||
@ -327,10 +337,11 @@ class _AttachmentZoomViewState extends State<AttachmentZoomView> {
|
||||
'${item.size} Bytes',
|
||||
style: metaTextStyle,
|
||||
),
|
||||
Text(
|
||||
'${item.metadata['width']}x${item.metadata['height']}',
|
||||
style: metaTextStyle,
|
||||
),
|
||||
if (item.metadata['width'] != null && item.metadata['height'] != null)
|
||||
Text(
|
||||
'${item.metadata['width']}x${item.metadata['height']}',
|
||||
style: metaTextStyle,
|
||||
),
|
||||
if (item.metadata['ratio'] != null)
|
||||
Text(
|
||||
(item.metadata['ratio'] as num).toStringAsFixed(2),
|
||||
|
@ -1,4 +1,4 @@
|
||||
import 'package:dropdown_button2/dropdown_button2.dart';
|
||||
import 'package:dropdown_button2/dropdown_button2.dart';
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:livekit_client/livekit_client.dart';
|
||||
@ -130,7 +130,7 @@ class _ChatCallPrejoinPopupState extends State<ChatCallPrejoinPopup> {
|
||||
Text('callCamera').tr(),
|
||||
Switch(
|
||||
value: call.enableVideo,
|
||||
onChanged: (value) => call.setEnableAudio(value),
|
||||
onChanged: call.setEnableVideo,
|
||||
),
|
||||
],
|
||||
).padding(bottom: 5),
|
||||
|
@ -10,6 +10,7 @@ import 'package:surface/providers/userinfo.dart';
|
||||
import 'package:surface/types/chat.dart';
|
||||
import 'package:surface/widgets/account/account_image.dart';
|
||||
import 'package:surface/widgets/attachment/attachment_list.dart';
|
||||
import 'package:surface/widgets/link_preview.dart';
|
||||
import 'package:surface/widgets/markdown_content.dart';
|
||||
import 'package:swipe_to/swipe_to.dart';
|
||||
|
||||
@ -22,6 +23,7 @@ class ChatMessage extends StatelessWidget {
|
||||
final Function(SnChatMessage)? onReply;
|
||||
final Function(SnChatMessage)? onEdit;
|
||||
final Function(SnChatMessage)? onDelete;
|
||||
|
||||
const ChatMessage({
|
||||
super.key,
|
||||
required this.data,
|
||||
@ -63,7 +65,7 @@ class ChatMessage extends StatelessWidget {
|
||||
onReply!(data);
|
||||
},
|
||||
),
|
||||
if (isOwner && onEdit != null)
|
||||
if (isOwner && data.type == 'messages.new' && onEdit != null)
|
||||
MenuItem(
|
||||
label: 'edit'.tr(),
|
||||
icon: Symbols.edit,
|
||||
@ -71,7 +73,7 @@ class ChatMessage extends StatelessWidget {
|
||||
onEdit!(data);
|
||||
},
|
||||
),
|
||||
if (isOwner && onDelete != null)
|
||||
if (isOwner && data.type == 'messages.new' && onDelete != null)
|
||||
MenuItem(
|
||||
label: 'delete'.tr(),
|
||||
icon: Symbols.delete,
|
||||
@ -109,9 +111,7 @@ class ChatMessage extends StatelessWidget {
|
||||
radius: 12,
|
||||
).padding(right: 6),
|
||||
Text(
|
||||
(data.sender.nick?.isNotEmpty ?? false)
|
||||
? data.sender.nick!
|
||||
: user?.nick ?? 'unknown',
|
||||
(data.sender.nick?.isNotEmpty ?? false) ? data.sender.nick! : user?.nick ?? 'unknown',
|
||||
).bold(),
|
||||
const Gap(6),
|
||||
Text(
|
||||
@ -123,8 +123,7 @@ class ChatMessage extends StatelessWidget {
|
||||
if (data.preload?.quoteEvent != null)
|
||||
StyledWidget(Container(
|
||||
decoration: BoxDecoration(
|
||||
borderRadius:
|
||||
const BorderRadius.all(Radius.circular(8)),
|
||||
borderRadius: const BorderRadius.all(Radius.circular(8)),
|
||||
border: Border.all(
|
||||
color: Theme.of(context).dividerColor,
|
||||
width: 1,
|
||||
@ -153,6 +152,8 @@ class ChatMessage extends StatelessWidget {
|
||||
)
|
||||
],
|
||||
).opacity(isPending ? 0.5 : 1),
|
||||
if (data.body['text'] != null && (data.body['text']?.isNotEmpty ?? false))
|
||||
LinkPreviewWidget(text: data.body['text']!),
|
||||
if (data.preload?.attachments?.isNotEmpty ?? false)
|
||||
AttachmentList(
|
||||
data: data.preload!.attachments!,
|
||||
@ -161,10 +162,7 @@ class ChatMessage extends StatelessWidget {
|
||||
maxHeight: 520,
|
||||
listPadding: const EdgeInsets.only(top: 8),
|
||||
),
|
||||
if (!hasMerged && !isCompact)
|
||||
const Gap(12)
|
||||
else if (!isCompact)
|
||||
const Gap(6),
|
||||
if (!hasMerged && !isCompact) const Gap(12) else if (!isCompact) const Gap(6),
|
||||
],
|
||||
),
|
||||
),
|
||||
@ -174,6 +172,7 @@ class ChatMessage extends StatelessWidget {
|
||||
|
||||
class _ChatMessageText extends StatelessWidget {
|
||||
final SnChatMessage data;
|
||||
|
||||
const _ChatMessageText({super.key, required this.data});
|
||||
|
||||
@override
|
||||
@ -184,6 +183,7 @@ class _ChatMessageText extends StatelessWidget {
|
||||
children: [
|
||||
MarkdownTextContent(
|
||||
content: data.body['text'],
|
||||
isSelectable: true,
|
||||
isAutoWarp: true,
|
||||
),
|
||||
if (data.updatedAt != data.createdAt)
|
||||
@ -212,6 +212,7 @@ class _ChatMessageText extends StatelessWidget {
|
||||
|
||||
class _ChatMessageSystemNotify extends StatelessWidget {
|
||||
final SnChatMessage data;
|
||||
|
||||
const _ChatMessageSystemNotify({super.key, required this.data});
|
||||
|
||||
String _formatDuration(Duration duration) {
|
||||
|
@ -12,13 +12,12 @@ import 'package:styled_widget/styled_widget.dart';
|
||||
import 'package:surface/controllers/chat_message_controller.dart';
|
||||
import 'package:surface/controllers/post_write_controller.dart';
|
||||
import 'package:surface/providers/sn_attachment.dart';
|
||||
import 'package:surface/providers/user_directory.dart';
|
||||
import 'package:surface/types/chat.dart';
|
||||
import 'package:surface/widgets/dialog.dart';
|
||||
import 'package:surface/widgets/markdown_content.dart';
|
||||
import 'package:surface/widgets/post/post_media_pending_list.dart';
|
||||
|
||||
import '../../providers/user_directory.dart';
|
||||
|
||||
class ChatMessageInput extends StatefulWidget {
|
||||
final ChatMessageController controller;
|
||||
final SnChannelMember? otherMember;
|
||||
@ -105,11 +104,6 @@ class ChatMessageInputState extends State<ChatMessageInput> {
|
||||
return;
|
||||
}
|
||||
|
||||
attach.putCache(
|
||||
_attachments.where((e) => e.attachment != null).map((e) => e.attachment!),
|
||||
noCheck: true,
|
||||
);
|
||||
|
||||
// Send the message
|
||||
// NOTICE This future should not be awaited, so that the message can be sent in the background and the user can continue to type
|
||||
widget.controller.sendMessage(
|
||||
|
@ -99,7 +99,7 @@ extension AppPromptExtension on BuildContext {
|
||||
|
||||
if (exception.response != null) {
|
||||
content = Text(
|
||||
'$preview\n\n(${exception.response?.statusCode}) ${exception.response?.data}',
|
||||
'$preview\n\n${exception.requestOptions.uri.path}\n(${exception.response?.statusCode}) ${exception.response?.data}',
|
||||
);
|
||||
} else {
|
||||
content = Text(preview);
|
||||
|
170
lib/widgets/link_preview.dart
Normal file
170
lib/widgets/link_preview.dart
Normal file
@ -0,0 +1,170 @@
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_svg/flutter_svg.dart';
|
||||
import 'package:gap/gap.dart';
|
||||
import 'package:google_fonts/google_fonts.dart';
|
||||
import 'package:marquee/marquee.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:responsive_framework/responsive_framework.dart';
|
||||
import 'package:styled_widget/styled_widget.dart';
|
||||
import 'package:surface/types/link.dart';
|
||||
import 'package:surface/widgets/universal_image.dart';
|
||||
import 'package:url_launcher/url_launcher_string.dart';
|
||||
|
||||
import '../providers/link_preview.dart';
|
||||
|
||||
class LinkPreviewWidget extends StatefulWidget {
|
||||
final String text;
|
||||
|
||||
const LinkPreviewWidget({super.key, required this.text});
|
||||
|
||||
@override
|
||||
State<LinkPreviewWidget> createState() => _LinkPreviewWidgetState();
|
||||
}
|
||||
|
||||
class _LinkPreviewWidgetState extends State<LinkPreviewWidget> {
|
||||
final List<SnLinkMeta> _links = List.empty(growable: true);
|
||||
|
||||
Future<void> _getLinkMeta() async {
|
||||
final linkRegex = RegExp(r'https?:\/\/[^\s/$.?#].[^\s]*');
|
||||
final links = linkRegex.allMatches(widget.text).map((e) => e.group(0)).toSet();
|
||||
|
||||
final lp = context.read<SnLinkPreviewProvider>();
|
||||
|
||||
final List<Future<SnLinkMeta?>> futures = links.where((e) => e != null).map((e) => lp.getLinkMeta(e!)).toList();
|
||||
final results = await Future.wait(futures);
|
||||
|
||||
_links.addAll(results.where((e) => e != null).map((e) => e!).toList());
|
||||
if (_links.isNotEmpty && mounted) setState(() {});
|
||||
}
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_getLinkMeta();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
if (_links.isEmpty) return const SizedBox.shrink();
|
||||
|
||||
return Wrap(
|
||||
spacing: 8,
|
||||
runSpacing: 8,
|
||||
children: _links.map((e) => _LinkPreviewEntry(meta: e)).toList(),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _LinkPreviewEntry extends StatelessWidget {
|
||||
final SnLinkMeta meta;
|
||||
|
||||
const _LinkPreviewEntry({
|
||||
super.key,
|
||||
required this.meta,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Container(
|
||||
constraints: BoxConstraints(
|
||||
maxWidth: ResponsiveBreakpoints.of(context).smallerOrEqualTo(MOBILE) ? double.infinity : 480,
|
||||
),
|
||||
child: GestureDetector(
|
||||
child: Card(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
if (meta.image != null)
|
||||
Container(
|
||||
margin: const EdgeInsets.only(bottom: 4),
|
||||
color: Theme.of(context).colorScheme.surfaceContainer,
|
||||
child: AspectRatio(
|
||||
aspectRatio: 16 / 9,
|
||||
child: ClipRRect(
|
||||
child: AutoResizeUniversalImage(
|
||||
meta.image!,
|
||||
fit: BoxFit.contain,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
SizedBox(
|
||||
height: 48,
|
||||
child: Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
children: [
|
||||
if (meta.icon?.isNotEmpty ?? false)
|
||||
StyledWidget(
|
||||
meta.icon!.endsWith('.svg')
|
||||
? SvgPicture.network(meta.icon!)
|
||||
: UniversalImage(
|
||||
meta.icon!,
|
||||
width: 36,
|
||||
height: 36,
|
||||
cacheHeight: 36,
|
||||
cacheWidth: 36,
|
||||
),
|
||||
).padding(all: 4, right: 16),
|
||||
Expanded(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
SizedBox(
|
||||
height: 24,
|
||||
child: ((meta.title?.length ?? 0) > 32)
|
||||
? Marquee(
|
||||
text: meta.title ?? 'unknown'.tr(),
|
||||
style: TextStyle(fontSize: 17),
|
||||
scrollAxis: Axis.horizontal,
|
||||
showFadingOnlyWhenScrolling: true,
|
||||
pauseAfterRound: const Duration(seconds: 3),
|
||||
)
|
||||
: Text(
|
||||
meta.title ?? 'unknown'.tr(),
|
||||
style: TextStyle(fontSize: 17),
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
),
|
||||
if (meta.siteName != null)
|
||||
Text(
|
||||
meta.siteName!,
|
||||
style: TextStyle(fontSize: 13, height: 0.9),
|
||||
).fontSize(11),
|
||||
],
|
||||
),
|
||||
),
|
||||
const Gap(6),
|
||||
],
|
||||
).padding(horizontal: 16),
|
||||
),
|
||||
if (meta.description != null)
|
||||
Text(
|
||||
meta.description!,
|
||||
maxLines: 3,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
).padding(horizontal: 16, bottom: 8),
|
||||
Text(
|
||||
meta.url,
|
||||
style: GoogleFonts.roboto(fontSize: 11, height: 0.9),
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
).opacity(0.75).padding(horizontal: 16),
|
||||
const Gap(4),
|
||||
Text(
|
||||
'poweredBy'.tr(args: ['HyperNet.Reader']),
|
||||
style: GoogleFonts.roboto(fontSize: 11, height: 0.9),
|
||||
).opacity(0.75).padding(horizontal: 16),
|
||||
const Gap(16),
|
||||
],
|
||||
),
|
||||
),
|
||||
onTap: () {
|
||||
launchUrlString(meta.url, mode: LaunchMode.externalApplication);
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
@ -7,12 +7,11 @@ import 'package:responsive_framework/responsive_framework.dart';
|
||||
|
||||
class AppBackground extends StatelessWidget {
|
||||
final Widget child;
|
||||
final bool isLessOptimization;
|
||||
final bool isRoot;
|
||||
|
||||
const AppBackground({
|
||||
super.key,
|
||||
required this.child,
|
||||
this.isLessOptimization = false,
|
||||
this.isRoot = false,
|
||||
});
|
||||
|
||||
@ -23,52 +22,25 @@ class AppBackground extends StatelessWidget {
|
||||
) {
|
||||
final devicePixelRatio = MediaQuery.of(context).devicePixelRatio;
|
||||
|
||||
if (isLessOptimization) {
|
||||
final size = MediaQuery.of(context).size;
|
||||
return Container(
|
||||
color: Theme.of(context).colorScheme.surface,
|
||||
child: Container(
|
||||
decoration: BoxDecoration(
|
||||
backgroundBlendMode: BlendMode.darken,
|
||||
color: Theme.of(context).colorScheme.surface,
|
||||
image: DecorationImage(
|
||||
opacity: 0.2,
|
||||
image: ResizeImage(
|
||||
FileImage(imageFile),
|
||||
width: (size.width * devicePixelRatio).round(),
|
||||
height: (size.height * devicePixelRatio).round(),
|
||||
policy: ResizeImagePolicy.fit,
|
||||
),
|
||||
fit: BoxFit.cover,
|
||||
),
|
||||
),
|
||||
child: child,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
final size = MediaQuery.of(context).size;
|
||||
return Container(
|
||||
color: Theme.of(context).colorScheme.surface,
|
||||
child: LayoutBuilder(
|
||||
builder: (context, constraints) {
|
||||
return Container(
|
||||
decoration: BoxDecoration(
|
||||
backgroundBlendMode: BlendMode.darken,
|
||||
color: Theme.of(context).colorScheme.surface,
|
||||
image: DecorationImage(
|
||||
opacity: 0.2,
|
||||
image: ResizeImage(
|
||||
FileImage(imageFile),
|
||||
width: (constraints.maxWidth * devicePixelRatio).round(),
|
||||
height: (constraints.maxHeight * devicePixelRatio).round(),
|
||||
policy: ResizeImagePolicy.fit,
|
||||
),
|
||||
fit: BoxFit.cover,
|
||||
),
|
||||
child: Container(
|
||||
decoration: BoxDecoration(
|
||||
backgroundBlendMode: BlendMode.darken,
|
||||
color: Theme.of(context).colorScheme.surface,
|
||||
image: DecorationImage(
|
||||
opacity: 0.2,
|
||||
image: ResizeImage(
|
||||
FileImage(imageFile),
|
||||
width: (size.width * devicePixelRatio).round(),
|
||||
height: (size.height * devicePixelRatio).round(),
|
||||
policy: ResizeImagePolicy.fit,
|
||||
),
|
||||
child: child,
|
||||
);
|
||||
},
|
||||
fit: BoxFit.cover,
|
||||
),
|
||||
),
|
||||
child: child,
|
||||
),
|
||||
);
|
||||
}
|
||||
@ -77,11 +49,9 @@ class AppBackground extends StatelessWidget {
|
||||
Widget build(BuildContext context) {
|
||||
return ScaffoldMessenger(
|
||||
child: FutureBuilder(
|
||||
future:
|
||||
kIsWeb ? Future.value(null) : getApplicationDocumentsDirectory(),
|
||||
future: kIsWeb ? Future.value(null) : getApplicationDocumentsDirectory(),
|
||||
builder: (context, snapshot) {
|
||||
if (isRoot ||
|
||||
ResponsiveBreakpoints.of(context).smallerOrEqualTo(MOBILE)) {
|
||||
if (isRoot || ResponsiveBreakpoints.of(context).smallerOrEqualTo(MOBILE)) {
|
||||
if (snapshot.hasData) {
|
||||
final path = '${snapshot.data!.path}/app_background_image';
|
||||
final file = File(path);
|
||||
|
@ -1,11 +1,11 @@
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
import 'package:package_info_plus/package_info_plus.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:responsive_framework/responsive_framework.dart';
|
||||
import 'package:styled_widget/styled_widget.dart';
|
||||
import 'package:surface/providers/navigation.dart';
|
||||
import 'package:surface/widgets/version_label.dart';
|
||||
|
||||
class AppNavigationDrawer extends StatefulWidget {
|
||||
final double? elevation;
|
||||
@ -49,14 +49,7 @@ class _AppNavigationDrawerState extends State<AppNavigationDrawer> {
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text('Solar Network').bold(),
|
||||
FutureBuilder<String>(
|
||||
future: PackageInfo.fromPlatform().then((value) => 'Stable ${value.version}+${value.buildNumber}'),
|
||||
builder: (context, snapshot) {
|
||||
return Text(!snapshot.hasData ? 'Stable 2.0' : snapshot.data!)
|
||||
.fontSize(12)
|
||||
.textColor(Theme.of(context).colorScheme.onSurface.withOpacity(0.5));
|
||||
},
|
||||
),
|
||||
AppVersionLabel(),
|
||||
],
|
||||
).padding(
|
||||
horizontal: 32,
|
||||
|
@ -2,13 +2,19 @@ import 'dart:io';
|
||||
import 'dart:math' as math;
|
||||
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:file_saver/file_saver.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
import 'package:google_fonts/google_fonts.dart';
|
||||
import 'package:material_symbols_icons/symbols.dart';
|
||||
import 'package:path_provider/path_provider.dart';
|
||||
import 'package:popover/popover.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:qr_flutter/qr_flutter.dart';
|
||||
import 'package:relative_time/relative_time.dart';
|
||||
import 'package:responsive_framework/responsive_framework.dart';
|
||||
import 'package:screenshot/screenshot.dart';
|
||||
import 'package:share_plus/share_plus.dart';
|
||||
import 'package:styled_widget/styled_widget.dart';
|
||||
import 'package:surface/providers/sn_network.dart';
|
||||
@ -18,6 +24,7 @@ import 'package:surface/types/reaction.dart';
|
||||
import 'package:surface/widgets/account/account_image.dart';
|
||||
import 'package:surface/widgets/attachment/attachment_list.dart';
|
||||
import 'package:surface/widgets/dialog.dart';
|
||||
import 'package:surface/widgets/link_preview.dart';
|
||||
import 'package:surface/widgets/markdown_content.dart';
|
||||
import 'package:gap/gap.dart';
|
||||
import 'package:surface/widgets/post/post_comment_list.dart';
|
||||
@ -52,10 +59,70 @@ class PostItem extends StatelessWidget {
|
||||
if (onChanged != null) onChanged!(data);
|
||||
}
|
||||
|
||||
void _doShare(BuildContext context) {
|
||||
final box = context.findRenderObject() as RenderBox?;
|
||||
final url = 'https://solsynth.dev/posts/${data.id}';
|
||||
if (!kIsWeb && (Platform.isAndroid || Platform.isIOS)) {
|
||||
Share.shareUri(Uri.parse(url), sharePositionOrigin: box!.localToGlobal(Offset.zero) & box.size);
|
||||
} else {
|
||||
Share.share(url, sharePositionOrigin: box!.localToGlobal(Offset.zero) & box.size);
|
||||
}
|
||||
}
|
||||
|
||||
void _doShareViaPicture(BuildContext context) async {
|
||||
final box = context.findRenderObject() as RenderBox?;
|
||||
context.showSnackbar('postSharingViaPicture'.tr());
|
||||
|
||||
final controller = ScreenshotController();
|
||||
final capturedImage = await controller.captureFromLongWidget(
|
||||
InheritedTheme.captureAll(
|
||||
context,
|
||||
MediaQuery(
|
||||
data: MediaQuery.of(context),
|
||||
child: Material(
|
||||
child: MultiProvider(
|
||||
providers: [
|
||||
Provider<SnNetworkProvider>(create: (_) => context.read()),
|
||||
],
|
||||
child: ResponsiveBreakpoints.builder(
|
||||
breakpoints: ResponsiveBreakpoints.of(context).breakpoints,
|
||||
child: PostShareImageWidget(data: data),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
pixelRatio: 3,
|
||||
context: context,
|
||||
);
|
||||
|
||||
if (kIsWeb) return;
|
||||
|
||||
final directory = await getTemporaryDirectory();
|
||||
final imageFile = await File(
|
||||
'${directory.path}/sn-share-via-image-${DateTime.now().millisecondsSinceEpoch}.png',
|
||||
).create();
|
||||
await imageFile.writeAsBytes(capturedImage);
|
||||
|
||||
if (!kIsWeb && (Platform.isAndroid || Platform.isIOS)) {
|
||||
await Share.shareXFiles(
|
||||
[XFile(imageFile.path)],
|
||||
sharePositionOrigin: box!.localToGlobal(Offset.zero) & box.size,
|
||||
);
|
||||
} else {
|
||||
await FileSaver.instance.saveFile(name: 'Solar Network Post #${data.id}', file: imageFile);
|
||||
}
|
||||
|
||||
await imageFile.delete();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final sn = context.read<SnNetworkProvider>();
|
||||
|
||||
final ua = context.read<UserProvider>();
|
||||
final isAuthor = ua.isAuthorized && data.publisher.accountId == ua.user?.id;
|
||||
|
||||
// Article headline preview
|
||||
if (!showFullPost && data.type == 'article') {
|
||||
return Container(
|
||||
@ -65,6 +132,10 @@ class PostItem extends StatelessWidget {
|
||||
children: [
|
||||
_PostContentHeader(
|
||||
data: data,
|
||||
isAuthor: isAuthor,
|
||||
isRelativeDate: !showFullPost,
|
||||
onShare: () => _doShare(context),
|
||||
onShareImage: () => _doShareViaPicture(context),
|
||||
onDeleted: () {
|
||||
if (onDeleted != null) {}
|
||||
},
|
||||
@ -116,6 +187,8 @@ class PostItem extends StatelessWidget {
|
||||
data: data,
|
||||
showComments: showComments,
|
||||
showReactions: showReactions,
|
||||
onShare: () => _doShare(context),
|
||||
onShareImage: () => _doShareViaPicture(context),
|
||||
onChanged: _onChanged,
|
||||
).padding(left: 8, right: 14),
|
||||
],
|
||||
@ -132,8 +205,12 @@ class PostItem extends StatelessWidget {
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
_PostContentHeader(
|
||||
isAuthor: isAuthor,
|
||||
isRelativeDate: !showFullPost,
|
||||
data: data,
|
||||
showMenu: showMenu,
|
||||
onShare: () => _doShare(context),
|
||||
onShareImage: () => _doShareViaPicture(context),
|
||||
onDeleted: () {
|
||||
if (onDeleted != null) onDeleted!();
|
||||
},
|
||||
@ -143,10 +220,12 @@ class PostItem extends StatelessWidget {
|
||||
data: data,
|
||||
isEnlarge: data.type == 'article' && showFullPost,
|
||||
).padding(horizontal: 16, bottom: 8),
|
||||
_PostContentBody(
|
||||
data: data,
|
||||
isEnlarge: data.type == 'article' && showFullPost,
|
||||
).padding(horizontal: 16, bottom: 6),
|
||||
if (data.body['content']?.isNotEmpty ?? false)
|
||||
_PostContentBody(
|
||||
data: data,
|
||||
isSelectable: showFullPost,
|
||||
isEnlarge: data.type == 'article' && showFullPost,
|
||||
).padding(horizontal: 16, bottom: 6),
|
||||
if (data.repostTo != null)
|
||||
_PostQuoteContent(child: data.repostTo!).padding(
|
||||
horizontal: 12,
|
||||
@ -173,6 +252,10 @@ class PostItem extends StatelessWidget {
|
||||
maxHeight: 560,
|
||||
listPadding: const EdgeInsets.symmetric(horizontal: 12),
|
||||
),
|
||||
if (data.body['content'] != null)
|
||||
LinkPreviewWidget(
|
||||
text: data.body['content'],
|
||||
).padding(horizontal: 4),
|
||||
Container(
|
||||
constraints: BoxConstraints(maxWidth: maxWidth ?? double.infinity),
|
||||
child: Column(
|
||||
@ -181,6 +264,8 @@ class PostItem extends StatelessWidget {
|
||||
data: data,
|
||||
showComments: showComments,
|
||||
showReactions: showReactions,
|
||||
onShare: () => _doShare(context),
|
||||
onShareImage: () => _doShareViaPicture(context),
|
||||
onChanged: _onChanged,
|
||||
).padding(left: 8, right: 14),
|
||||
],
|
||||
@ -191,28 +276,173 @@ class PostItem extends StatelessWidget {
|
||||
}
|
||||
}
|
||||
|
||||
class PostShareImageWidget extends StatelessWidget {
|
||||
const PostShareImageWidget({
|
||||
super.key,
|
||||
required this.data,
|
||||
});
|
||||
|
||||
final SnPost data;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final sn = context.read<SnNetworkProvider>();
|
||||
|
||||
return SizedBox(
|
||||
width: 480,
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
if (data.preload?.thumbnail != null)
|
||||
AspectRatio(
|
||||
aspectRatio: 16 / 9,
|
||||
child: ClipRRect(
|
||||
borderRadius: const BorderRadius.only(
|
||||
topLeft: Radius.circular(8),
|
||||
topRight: Radius.circular(8),
|
||||
),
|
||||
child: AutoResizeUniversalImage(
|
||||
sn.getAttachmentUrl(data.preload!.thumbnail!.rid),
|
||||
fit: BoxFit.cover,
|
||||
),
|
||||
),
|
||||
).padding(bottom: 8),
|
||||
_PostContentHeader(
|
||||
isAuthor: false,
|
||||
data: data,
|
||||
onDeleted: () {},
|
||||
onShare: () {},
|
||||
onShareImage: () {},
|
||||
showMenu: false,
|
||||
isRelativeDate: false,
|
||||
).padding(horizontal: 16, bottom: 8),
|
||||
_PostHeadline(
|
||||
data: data,
|
||||
isEnlarge: data.type == 'article',
|
||||
).padding(horizontal: 16, bottom: 8),
|
||||
if (data.body['content']?.isNotEmpty ?? false)
|
||||
_PostContentBody(
|
||||
data: data,
|
||||
isEnlarge: data.type == 'article',
|
||||
).padding(horizontal: 16, bottom: 8),
|
||||
if (data.repostTo != null)
|
||||
_PostQuoteContent(
|
||||
child: data.repostTo!,
|
||||
isRelativeDate: false,
|
||||
isFlatted: true,
|
||||
).padding(horizontal: 16, bottom: 8),
|
||||
if (data.type != 'article' && (data.preload?.attachments?.isNotEmpty ?? false))
|
||||
AttachmentList(
|
||||
data: data.preload!.attachments!,
|
||||
isFlatted: true,
|
||||
).padding(horizontal: 16, bottom: 8),
|
||||
if (data.body['content'] != null)
|
||||
LinkPreviewWidget(
|
||||
text: data.body['content'],
|
||||
).padding(horizontal: 4),
|
||||
Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
if (data.visibility > 0) _PostVisibilityHint(data: data),
|
||||
if (data.body['content_truncated'] == true) _PostTruncatedHint(data: data),
|
||||
],
|
||||
).padding(horizontal: 16),
|
||||
_PostBottomAction(
|
||||
data: data,
|
||||
showComments: true,
|
||||
showReactions: true,
|
||||
onShare: () {},
|
||||
onShareImage: () {},
|
||||
onChanged: (SnPost data) {},
|
||||
).padding(left: 8, right: 14),
|
||||
const Divider(height: 1),
|
||||
const Gap(12),
|
||||
SizedBox(
|
||||
height: 100,
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
Expanded(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Expanded(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
'${data.aliasPrefix} / ${data.alias ?? '#${data.id}'}',
|
||||
style: GoogleFonts.robotoMono(fontSize: 17),
|
||||
),
|
||||
const Gap(2),
|
||||
Text(
|
||||
switch (data.type) {
|
||||
'article' => 'postArticle'.tr(),
|
||||
_ => 'postStory'.tr(),
|
||||
},
|
||||
style: GoogleFonts.robotoMono(fontSize: 12),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
if (data.body['content_truncated'] == true)
|
||||
Text(
|
||||
'postImageShareReadMore'.tr(),
|
||||
style: GoogleFonts.robotoMono(fontSize: 11),
|
||||
),
|
||||
Text(
|
||||
'postImageShareAds',
|
||||
style: GoogleFonts.robotoMono(fontSize: 13),
|
||||
).tr(),
|
||||
],
|
||||
),
|
||||
),
|
||||
QrImageView(
|
||||
padding: EdgeInsets.zero,
|
||||
data: 'https://solsynth.dev/posts/${data.id}',
|
||||
errorCorrectionLevel: QrErrorCorrectLevel.H,
|
||||
version: QrVersions.auto,
|
||||
size: 100,
|
||||
gapless: true,
|
||||
embeddedImage: AssetImage('assets/icon/icon-light-radius.png'),
|
||||
embeddedImageStyle: QrEmbeddedImageStyle(
|
||||
size: Size(28, 28),
|
||||
),
|
||||
eyeStyle: QrEyeStyle(
|
||||
eyeShape: QrEyeShape.square,
|
||||
color: Theme.of(context).colorScheme.onSurface,
|
||||
),
|
||||
dataModuleStyle: QrDataModuleStyle(
|
||||
dataModuleShape: QrDataModuleShape.square,
|
||||
color: Theme.of(context).colorScheme.onSurface,
|
||||
),
|
||||
)
|
||||
],
|
||||
),
|
||||
).padding(left: 16, right: 32, vertical: 8),
|
||||
],
|
||||
).padding(vertical: 16),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _PostBottomAction extends StatelessWidget {
|
||||
final SnPost data;
|
||||
final bool showComments;
|
||||
final bool showReactions;
|
||||
final Function(SnPost data) onChanged;
|
||||
final Function() onShare, onShareImage;
|
||||
|
||||
const _PostBottomAction({
|
||||
required this.data,
|
||||
required this.showComments,
|
||||
required this.showReactions,
|
||||
required this.onChanged,
|
||||
required this.onShare,
|
||||
required this.onShareImage,
|
||||
});
|
||||
|
||||
void _doShare() {
|
||||
final url = 'https://solsynth.dev/posts/${data.id}';
|
||||
if (!kIsWeb && (Platform.isAndroid || Platform.isIOS)) {
|
||||
Share.shareUri(Uri.parse(url));
|
||||
} else {
|
||||
Share.share(url);
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final iconColor = Theme.of(context).colorScheme.onSurface.withAlpha(
|
||||
@ -228,6 +458,7 @@ class _PostBottomAction extends StatelessWidget {
|
||||
children: [
|
||||
if (showReactions || showComments)
|
||||
Row(
|
||||
spacing: 8,
|
||||
children: [
|
||||
if (showReactions)
|
||||
InkWell(
|
||||
@ -293,11 +524,11 @@ class _PostBottomAction extends StatelessWidget {
|
||||
);
|
||||
},
|
||||
),
|
||||
].expand((ele) => [ele, const Gap(8)]).toList()
|
||||
..removeLast(),
|
||||
],
|
||||
),
|
||||
InkWell(
|
||||
onTap: _doShare,
|
||||
onTap: onShare,
|
||||
onLongPress: onShareImage,
|
||||
child: Icon(
|
||||
Symbols.share,
|
||||
size: 20,
|
||||
@ -369,10 +600,10 @@ class _PostHeadline extends StatelessWidget {
|
||||
style: TextStyle(fontSize: 13),
|
||||
),
|
||||
const Gap(8),
|
||||
if (data.updatedAt != data.createdAt)
|
||||
if (data.editedAt != null)
|
||||
Text(
|
||||
'articleEditedAt'.tr(
|
||||
args: [DateFormat('y/M/d HH:mm').format(data.updatedAt)],
|
||||
args: [DateFormat('y/M/d HH:mm').format(data.editedAt!)],
|
||||
),
|
||||
style: TextStyle(fontSize: 13),
|
||||
),
|
||||
@ -405,15 +636,22 @@ class _PostHeadline extends StatelessWidget {
|
||||
|
||||
class _PostContentHeader extends StatelessWidget {
|
||||
final SnPost data;
|
||||
final bool isAuthor;
|
||||
final bool isCompact;
|
||||
final bool isRelativeDate;
|
||||
final bool showMenu;
|
||||
final Function onDeleted;
|
||||
final Function() onShare, onShareImage;
|
||||
|
||||
const _PostContentHeader({
|
||||
required this.data,
|
||||
required this.isAuthor,
|
||||
this.isCompact = false,
|
||||
this.isRelativeDate = true,
|
||||
this.showMenu = true,
|
||||
required this.onDeleted,
|
||||
required this.onShare,
|
||||
required this.onShareImage,
|
||||
});
|
||||
|
||||
Future<void> _deletePost(BuildContext context) async {
|
||||
@ -441,9 +679,6 @@ class _PostContentHeader extends StatelessWidget {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final ua = context.read<UserProvider>();
|
||||
final isAuthor = ua.isAuthorized && data.publisher.accountId == ua.user!.id;
|
||||
|
||||
return Row(
|
||||
children: [
|
||||
GestureDetector(
|
||||
@ -479,9 +714,11 @@ class _PostContentHeader extends StatelessWidget {
|
||||
children: [
|
||||
Text('@${data.publisher.name}').fontSize(13),
|
||||
const Gap(4),
|
||||
Text(RelativeTime(context).format(
|
||||
data.publishedAt ?? data.createdAt,
|
||||
)).fontSize(13),
|
||||
Text(
|
||||
isRelativeDate
|
||||
? RelativeTime(context).format(data.publishedAt ?? data.createdAt)
|
||||
: DateFormat('y/M/d HH:mm').format(data.publishedAt ?? data.createdAt),
|
||||
).fontSize(13),
|
||||
],
|
||||
).opacity(0.8),
|
||||
],
|
||||
@ -496,9 +733,11 @@ class _PostContentHeader extends StatelessWidget {
|
||||
children: [
|
||||
Text('@${data.publisher.name}').fontSize(13),
|
||||
const Gap(4),
|
||||
Text(RelativeTime(context).format(
|
||||
data.publishedAt ?? data.createdAt,
|
||||
)).fontSize(13),
|
||||
Text(
|
||||
isRelativeDate
|
||||
? RelativeTime(context).format(data.publishedAt ?? data.createdAt)
|
||||
: DateFormat('y/M/d HH:mm').format(data.publishedAt ?? data.createdAt),
|
||||
).fontSize(13),
|
||||
],
|
||||
).opacity(0.8),
|
||||
],
|
||||
@ -573,6 +812,27 @@ class _PostContentHeader extends StatelessWidget {
|
||||
},
|
||||
),
|
||||
const PopupMenuDivider(),
|
||||
PopupMenuItem(
|
||||
onTap: onShare,
|
||||
child: Row(
|
||||
children: [
|
||||
const Icon(Symbols.share),
|
||||
const Gap(16),
|
||||
Text('postShare').tr(),
|
||||
],
|
||||
),
|
||||
),
|
||||
PopupMenuItem(
|
||||
onTap: onShareImage,
|
||||
child: Row(
|
||||
children: [
|
||||
const Icon(Symbols.share_reviews),
|
||||
const Gap(16),
|
||||
Text('postShareImage').tr(),
|
||||
],
|
||||
),
|
||||
),
|
||||
const PopupMenuDivider(),
|
||||
PopupMenuItem(
|
||||
child: Row(
|
||||
children: [
|
||||
@ -604,16 +864,19 @@ class _PostContentHeader extends StatelessWidget {
|
||||
class _PostContentBody extends StatelessWidget {
|
||||
final SnPost data;
|
||||
final bool isEnlarge;
|
||||
final bool isSelectable;
|
||||
|
||||
const _PostContentBody({
|
||||
required this.data,
|
||||
this.isEnlarge = false,
|
||||
this.isSelectable = false,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
if (data.body['content'] == null) return const SizedBox.shrink();
|
||||
return MarkdownTextContent(
|
||||
isSelectable: isSelectable,
|
||||
textScaler: isEnlarge ? TextScaler.linear(1.1) : null,
|
||||
content: data.body['content'],
|
||||
attachments: data.preload?.attachments,
|
||||
@ -623,8 +886,15 @@ class _PostContentBody extends StatelessWidget {
|
||||
|
||||
class _PostQuoteContent extends StatelessWidget {
|
||||
final SnPost child;
|
||||
final bool isRelativeDate;
|
||||
final bool isFlatted;
|
||||
|
||||
const _PostQuoteContent({super.key, required this.child});
|
||||
const _PostQuoteContent({
|
||||
super.key,
|
||||
this.isRelativeDate = true,
|
||||
this.isFlatted = false,
|
||||
required this.child,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
@ -637,16 +907,42 @@ class _PostQuoteContent extends StatelessWidget {
|
||||
width: 1,
|
||||
),
|
||||
),
|
||||
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
|
||||
padding: const EdgeInsets.only(top: 8),
|
||||
child: Column(
|
||||
children: [
|
||||
_PostContentHeader(
|
||||
data: child,
|
||||
isCompact: true,
|
||||
showMenu: false,
|
||||
onDeleted: () {},
|
||||
).padding(bottom: 4),
|
||||
_PostContentBody(data: child),
|
||||
Column(
|
||||
children: [
|
||||
_PostContentHeader(
|
||||
isAuthor: false,
|
||||
data: child,
|
||||
isCompact: true,
|
||||
isRelativeDate: isRelativeDate,
|
||||
showMenu: false,
|
||||
onShare: () {},
|
||||
onShareImage: () {},
|
||||
onDeleted: () {},
|
||||
).padding(bottom: 4),
|
||||
_PostContentBody(data: child),
|
||||
if (child.visibility > 0) _PostVisibilityHint(data: child).padding(top: 4),
|
||||
],
|
||||
).padding(horizontal: 16),
|
||||
if (child.type != 'article' && (child.preload?.attachments?.isNotEmpty ?? false))
|
||||
ClipRRect(
|
||||
borderRadius: const BorderRadius.only(
|
||||
bottomLeft: Radius.circular(8),
|
||||
bottomRight: Radius.circular(8),
|
||||
),
|
||||
child: AttachmentList(
|
||||
data: child.preload!.attachments!,
|
||||
isFlatted: isFlatted,
|
||||
listPadding: const EdgeInsets.symmetric(horizontal: 12),
|
||||
),
|
||||
).padding(
|
||||
top: 8,
|
||||
bottom: (child.preload?.attachments?.length ?? 0) > 1 ? 12 : 0,
|
||||
)
|
||||
else
|
||||
const Gap(8),
|
||||
],
|
||||
),
|
||||
),
|
||||
|
@ -35,7 +35,7 @@ class _PostMiniEditorState extends State<PostMiniEditor> {
|
||||
|
||||
try {
|
||||
final sn = context.read<SnNetworkProvider>();
|
||||
final resp = await sn.client.get('/cgi/co/publishers');
|
||||
final resp = await sn.client.get('/cgi/co/publishers/me');
|
||||
_publishers = List<SnPublisher>.from(
|
||||
resp.data?.map((e) => SnPublisher.fromJson(e)) ?? [],
|
||||
);
|
||||
|
@ -1,12 +1,15 @@
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:cached_network_image/cached_network_image.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:material_symbols_icons/symbols.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:styled_widget/styled_widget.dart';
|
||||
import 'package:flutter_animate/flutter_animate.dart';
|
||||
|
||||
// Keep this import to make the web image render work
|
||||
import 'package:cached_network_image_platform_interface/cached_network_image_platform_interface.dart';
|
||||
import 'package:surface/providers/config.dart';
|
||||
|
||||
class UniversalImage extends StatelessWidget {
|
||||
final String url;
|
||||
final double? width, height;
|
||||
@ -14,6 +17,7 @@ class UniversalImage extends StatelessWidget {
|
||||
final bool noProgressIndicator;
|
||||
final bool noErrorWidget;
|
||||
final double? cacheWidth, cacheHeight;
|
||||
final FilterQuality? filterQuality;
|
||||
|
||||
const UniversalImage(
|
||||
this.url, {
|
||||
@ -25,45 +29,43 @@ class UniversalImage extends StatelessWidget {
|
||||
this.noErrorWidget = false,
|
||||
this.cacheWidth,
|
||||
this.cacheHeight,
|
||||
this.filterQuality,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final devicePixelRatio = MediaQuery.of(context).devicePixelRatio;
|
||||
final double? resizeHeight =
|
||||
cacheHeight != null ? (cacheHeight! * devicePixelRatio) : null;
|
||||
final double? resizeWidth =
|
||||
cacheWidth != null ? (cacheWidth! * devicePixelRatio) : null;
|
||||
final double? resizeHeight = cacheHeight != null ? (cacheHeight! * devicePixelRatio) : null;
|
||||
final double? resizeWidth = cacheWidth != null ? (cacheWidth! * devicePixelRatio) : null;
|
||||
|
||||
return Image(
|
||||
image: ResizeImage(
|
||||
UniversalImage.provider(url),
|
||||
width: resizeWidth?.round(),
|
||||
height: resizeHeight?.round(),
|
||||
policy: ResizeImagePolicy.fit,
|
||||
),
|
||||
filterQuality: filterQuality ?? context.read<ConfigProvider>().imageQuality,
|
||||
image: kIsWeb
|
||||
? UniversalImage.provider(url)
|
||||
: ResizeImage(
|
||||
UniversalImage.provider(url),
|
||||
width: resizeWidth?.round(),
|
||||
height: resizeHeight?.round(),
|
||||
policy: ResizeImagePolicy.fit,
|
||||
),
|
||||
width: width,
|
||||
height: height,
|
||||
fit: fit,
|
||||
loadingBuilder: noProgressIndicator
|
||||
? null
|
||||
: (BuildContext context, Widget child,
|
||||
ImageChunkEvent? loadingProgress) {
|
||||
: (BuildContext context, Widget child, ImageChunkEvent? loadingProgress) {
|
||||
if (loadingProgress == null) return child;
|
||||
return Center(
|
||||
child: TweenAnimationBuilder(
|
||||
tween: Tween(
|
||||
begin: 0,
|
||||
end: loadingProgress.expectedTotalBytes != null
|
||||
? loadingProgress.cumulativeBytesLoaded /
|
||||
loadingProgress.expectedTotalBytes!
|
||||
? loadingProgress.cumulativeBytesLoaded / loadingProgress.expectedTotalBytes!
|
||||
: 0,
|
||||
),
|
||||
duration: const Duration(milliseconds: 300),
|
||||
builder: (context, value, _) => CircularProgressIndicator(
|
||||
value: loadingProgress.expectedTotalBytes != null
|
||||
? value.toDouble()
|
||||
: null,
|
||||
value: loadingProgress.expectedTotalBytes != null ? value.toDouble() : null,
|
||||
),
|
||||
),
|
||||
);
|
||||
@ -94,10 +96,13 @@ class UniversalImage extends StatelessWidget {
|
||||
}
|
||||
|
||||
static ImageProvider provider(String url) {
|
||||
if (!kIsWeb && (Platform.isAndroid || Platform.isIOS || Platform.isMacOS)) {
|
||||
return CachedNetworkImageProvider(url);
|
||||
}
|
||||
return NetworkImage(url);
|
||||
// This place used to use network image or cached network image depending on the platform.
|
||||
// But now the cached network image is working on every platform.
|
||||
// So we just use it now.
|
||||
return CachedNetworkImageProvider(
|
||||
url,
|
||||
imageRenderMethodForWeb: ImageRenderMethodForWeb.HttpGet,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
20
lib/widgets/version_label.dart
Normal file
20
lib/widgets/version_label.dart
Normal file
@ -0,0 +1,20 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:package_info_plus/package_info_plus.dart';
|
||||
import 'package:styled_widget/styled_widget.dart';
|
||||
|
||||
class AppVersionLabel extends StatelessWidget {
|
||||
final double fontSize;
|
||||
const AppVersionLabel({super.key, this.fontSize = 12});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return FutureBuilder<String>(
|
||||
future: PackageInfo.fromPlatform().then((value) => 'Stable ${value.version}+${value.buildNumber}'),
|
||||
builder: (context, snapshot) {
|
||||
return Text(!snapshot.hasData ? 'Stable 2.0' : snapshot.data!).fontSize(fontSize).textColor(
|
||||
Theme.of(context).colorScheme.onSurface.withOpacity(0.5),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
@ -7,19 +7,22 @@
|
||||
#include "generated_plugin_registrant.h"
|
||||
|
||||
#include <bitsdojo_window_linux/bitsdojo_window_plugin.h>
|
||||
#include <file_saver/file_saver_plugin.h>
|
||||
#include <file_selector_linux/file_selector_plugin.h>
|
||||
#include <flutter_udid/flutter_udid_plugin.h>
|
||||
#include <flutter_webrtc/flutter_web_r_t_c_plugin.h>
|
||||
#include <media_kit_libs_linux/media_kit_libs_linux_plugin.h>
|
||||
#include <media_kit_video/media_kit_video_plugin.h>
|
||||
#include <pasteboard/pasteboard_plugin.h>
|
||||
#include <sentry_flutter/sentry_flutter_plugin.h>
|
||||
#include <url_launcher_linux/url_launcher_plugin.h>
|
||||
|
||||
void fl_register_plugins(FlPluginRegistry* registry) {
|
||||
g_autoptr(FlPluginRegistrar) bitsdojo_window_linux_registrar =
|
||||
fl_plugin_registry_get_registrar_for_plugin(registry, "BitsdojoWindowPlugin");
|
||||
bitsdojo_window_plugin_register_with_registrar(bitsdojo_window_linux_registrar);
|
||||
g_autoptr(FlPluginRegistrar) file_saver_registrar =
|
||||
fl_plugin_registry_get_registrar_for_plugin(registry, "FileSaverPlugin");
|
||||
file_saver_plugin_register_with_registrar(file_saver_registrar);
|
||||
g_autoptr(FlPluginRegistrar) file_selector_linux_registrar =
|
||||
fl_plugin_registry_get_registrar_for_plugin(registry, "FileSelectorPlugin");
|
||||
file_selector_plugin_register_with_registrar(file_selector_linux_registrar);
|
||||
@ -38,9 +41,6 @@ void fl_register_plugins(FlPluginRegistry* registry) {
|
||||
g_autoptr(FlPluginRegistrar) pasteboard_registrar =
|
||||
fl_plugin_registry_get_registrar_for_plugin(registry, "PasteboardPlugin");
|
||||
pasteboard_plugin_register_with_registrar(pasteboard_registrar);
|
||||
g_autoptr(FlPluginRegistrar) sentry_flutter_registrar =
|
||||
fl_plugin_registry_get_registrar_for_plugin(registry, "SentryFlutterPlugin");
|
||||
sentry_flutter_plugin_register_with_registrar(sentry_flutter_registrar);
|
||||
g_autoptr(FlPluginRegistrar) url_launcher_linux_registrar =
|
||||
fl_plugin_registry_get_registrar_for_plugin(registry, "UrlLauncherPlugin");
|
||||
url_launcher_plugin_register_with_registrar(url_launcher_linux_registrar);
|
||||
|
@ -4,19 +4,18 @@
|
||||
|
||||
list(APPEND FLUTTER_PLUGIN_LIST
|
||||
bitsdojo_window_linux
|
||||
file_saver
|
||||
file_selector_linux
|
||||
flutter_udid
|
||||
flutter_webrtc
|
||||
media_kit_libs_linux
|
||||
media_kit_video
|
||||
pasteboard
|
||||
sentry_flutter
|
||||
url_launcher_linux
|
||||
)
|
||||
|
||||
list(APPEND FLUTTER_FFI_PLUGIN_LIST
|
||||
croppy
|
||||
jni
|
||||
media_kit_native_event_loop
|
||||
)
|
||||
|
||||
|
@ -8,6 +8,7 @@ import Foundation
|
||||
import bitsdojo_window_macos
|
||||
import connectivity_plus
|
||||
import device_info_plus
|
||||
import file_saver
|
||||
import file_selector_macos
|
||||
import firebase_analytics
|
||||
import firebase_core
|
||||
@ -15,6 +16,7 @@ import firebase_messaging
|
||||
import flutter_udid
|
||||
import flutter_webrtc
|
||||
import gal
|
||||
import in_app_review
|
||||
import livekit_client
|
||||
import media_kit_libs_macos_video
|
||||
import media_kit_video
|
||||
@ -22,7 +24,6 @@ import package_info_plus
|
||||
import pasteboard
|
||||
import path_provider_foundation
|
||||
import screen_brightness_macos
|
||||
import sentry_flutter
|
||||
import share_plus
|
||||
import shared_preferences_foundation
|
||||
import sqflite_darwin
|
||||
@ -33,6 +34,7 @@ func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) {
|
||||
BitsdojoWindowPlugin.register(with: registry.registrar(forPlugin: "BitsdojoWindowPlugin"))
|
||||
ConnectivityPlusPlugin.register(with: registry.registrar(forPlugin: "ConnectivityPlusPlugin"))
|
||||
DeviceInfoPlusMacosPlugin.register(with: registry.registrar(forPlugin: "DeviceInfoPlusMacosPlugin"))
|
||||
FileSaverPlugin.register(with: registry.registrar(forPlugin: "FileSaverPlugin"))
|
||||
FileSelectorPlugin.register(with: registry.registrar(forPlugin: "FileSelectorPlugin"))
|
||||
FLTFirebaseAnalyticsPlugin.register(with: registry.registrar(forPlugin: "FLTFirebaseAnalyticsPlugin"))
|
||||
FLTFirebaseCorePlugin.register(with: registry.registrar(forPlugin: "FLTFirebaseCorePlugin"))
|
||||
@ -40,6 +42,7 @@ func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) {
|
||||
FlutterUdidPlugin.register(with: registry.registrar(forPlugin: "FlutterUdidPlugin"))
|
||||
FlutterWebRTCPlugin.register(with: registry.registrar(forPlugin: "FlutterWebRTCPlugin"))
|
||||
GalPlugin.register(with: registry.registrar(forPlugin: "GalPlugin"))
|
||||
InAppReviewPlugin.register(with: registry.registrar(forPlugin: "InAppReviewPlugin"))
|
||||
LiveKitPlugin.register(with: registry.registrar(forPlugin: "LiveKitPlugin"))
|
||||
MediaKitLibsMacosVideoPlugin.register(with: registry.registrar(forPlugin: "MediaKitLibsMacosVideoPlugin"))
|
||||
MediaKitVideoPlugin.register(with: registry.registrar(forPlugin: "MediaKitVideoPlugin"))
|
||||
@ -47,7 +50,6 @@ func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) {
|
||||
PasteboardPlugin.register(with: registry.registrar(forPlugin: "PasteboardPlugin"))
|
||||
PathProviderPlugin.register(with: registry.registrar(forPlugin: "PathProviderPlugin"))
|
||||
ScreenBrightnessMacosPlugin.register(with: registry.registrar(forPlugin: "ScreenBrightnessMacosPlugin"))
|
||||
SentryFlutterPlugin.register(with: registry.registrar(forPlugin: "SentryFlutterPlugin"))
|
||||
SharePlusMacosPlugin.register(with: registry.registrar(forPlugin: "SharePlusMacosPlugin"))
|
||||
SharedPreferencesPlugin.register(with: registry.registrar(forPlugin: "SharedPreferencesPlugin"))
|
||||
SqflitePlugin.register(with: registry.registrar(forPlugin: "SqflitePlugin"))
|
||||
|
@ -6,10 +6,10 @@ PODS:
|
||||
- FlutterMacOS
|
||||
- croppy (0.0.1):
|
||||
- FlutterMacOS
|
||||
- cupertino_http (0.0.1):
|
||||
- FlutterMacOS
|
||||
- device_info_plus (0.0.1):
|
||||
- FlutterMacOS
|
||||
- file_saver (0.0.1):
|
||||
- FlutterMacOS
|
||||
- file_selector_macos (0.0.1):
|
||||
- FlutterMacOS
|
||||
- Firebase/Analytics (11.4.0):
|
||||
@ -26,7 +26,7 @@ PODS:
|
||||
- Firebase/Analytics (= 11.4.0)
|
||||
- firebase_core
|
||||
- FlutterMacOS
|
||||
- firebase_core (3.8.1):
|
||||
- firebase_core (3.9.0):
|
||||
- Firebase/CoreOnly (~> 11.4.0)
|
||||
- FlutterMacOS
|
||||
- firebase_messaging (15.1.6):
|
||||
@ -56,7 +56,7 @@ PODS:
|
||||
- FirebaseCoreInternal (~> 11.0)
|
||||
- GoogleUtilities/Environment (~> 8.0)
|
||||
- GoogleUtilities/Logger (~> 8.0)
|
||||
- FirebaseCoreInternal (11.5.0):
|
||||
- FirebaseCoreInternal (11.6.0):
|
||||
- "GoogleUtilities/NSData+zlib (~> 8.0)"
|
||||
- FirebaseInstallations (11.4.0):
|
||||
- FirebaseCore (~> 11.0)
|
||||
@ -132,7 +132,8 @@ PODS:
|
||||
- GoogleUtilities/UserDefaults (8.0.2):
|
||||
- GoogleUtilities/Logger
|
||||
- GoogleUtilities/Privacy
|
||||
- livekit_client (2.3.1):
|
||||
- livekit_client (2.3.2):
|
||||
- flutter_webrtc
|
||||
- FlutterMacOS
|
||||
- WebRTC-SDK (= 125.6422.06)
|
||||
- media_kit_libs_macos_video (1.0.4):
|
||||
@ -157,11 +158,6 @@ PODS:
|
||||
- SAMKeychain (1.5.3)
|
||||
- screen_brightness_macos (0.1.0):
|
||||
- FlutterMacOS
|
||||
- Sentry/HybridSDK (8.40.1)
|
||||
- sentry_flutter (8.10.1):
|
||||
- Flutter
|
||||
- FlutterMacOS
|
||||
- Sentry/HybridSDK (= 8.40.1)
|
||||
- share_plus (0.0.1):
|
||||
- FlutterMacOS
|
||||
- shared_preferences_foundation (0.0.1):
|
||||
@ -180,8 +176,8 @@ DEPENDENCIES:
|
||||
- bitsdojo_window_macos (from `Flutter/ephemeral/.symlinks/plugins/bitsdojo_window_macos/macos`)
|
||||
- connectivity_plus (from `Flutter/ephemeral/.symlinks/plugins/connectivity_plus/darwin`)
|
||||
- croppy (from `Flutter/ephemeral/.symlinks/plugins/croppy/macos`)
|
||||
- cupertino_http (from `Flutter/ephemeral/.symlinks/plugins/cupertino_http/macos`)
|
||||
- device_info_plus (from `Flutter/ephemeral/.symlinks/plugins/device_info_plus/macos`)
|
||||
- file_saver (from `Flutter/ephemeral/.symlinks/plugins/file_saver/macos`)
|
||||
- file_selector_macos (from `Flutter/ephemeral/.symlinks/plugins/file_selector_macos/macos`)
|
||||
- firebase_analytics (from `Flutter/ephemeral/.symlinks/plugins/firebase_analytics/macos`)
|
||||
- firebase_core (from `Flutter/ephemeral/.symlinks/plugins/firebase_core/macos`)
|
||||
@ -198,7 +194,6 @@ DEPENDENCIES:
|
||||
- pasteboard (from `Flutter/ephemeral/.symlinks/plugins/pasteboard/macos`)
|
||||
- path_provider_foundation (from `Flutter/ephemeral/.symlinks/plugins/path_provider_foundation/darwin`)
|
||||
- screen_brightness_macos (from `Flutter/ephemeral/.symlinks/plugins/screen_brightness_macos/macos`)
|
||||
- sentry_flutter (from `Flutter/ephemeral/.symlinks/plugins/sentry_flutter/macos`)
|
||||
- share_plus (from `Flutter/ephemeral/.symlinks/plugins/share_plus/macos`)
|
||||
- shared_preferences_foundation (from `Flutter/ephemeral/.symlinks/plugins/shared_preferences_foundation/darwin`)
|
||||
- sqflite_darwin (from `Flutter/ephemeral/.symlinks/plugins/sqflite_darwin/darwin`)
|
||||
@ -219,7 +214,6 @@ SPEC REPOS:
|
||||
- nanopb
|
||||
- PromisesObjC
|
||||
- SAMKeychain
|
||||
- Sentry
|
||||
- WebRTC-SDK
|
||||
|
||||
EXTERNAL SOURCES:
|
||||
@ -229,10 +223,10 @@ EXTERNAL SOURCES:
|
||||
:path: Flutter/ephemeral/.symlinks/plugins/connectivity_plus/darwin
|
||||
croppy:
|
||||
:path: Flutter/ephemeral/.symlinks/plugins/croppy/macos
|
||||
cupertino_http:
|
||||
:path: Flutter/ephemeral/.symlinks/plugins/cupertino_http/macos
|
||||
device_info_plus:
|
||||
:path: Flutter/ephemeral/.symlinks/plugins/device_info_plus/macos
|
||||
file_saver:
|
||||
:path: Flutter/ephemeral/.symlinks/plugins/file_saver/macos
|
||||
file_selector_macos:
|
||||
:path: Flutter/ephemeral/.symlinks/plugins/file_selector_macos/macos
|
||||
firebase_analytics:
|
||||
@ -265,8 +259,6 @@ EXTERNAL SOURCES:
|
||||
:path: Flutter/ephemeral/.symlinks/plugins/path_provider_foundation/darwin
|
||||
screen_brightness_macos:
|
||||
:path: Flutter/ephemeral/.symlinks/plugins/screen_brightness_macos/macos
|
||||
sentry_flutter:
|
||||
:path: Flutter/ephemeral/.symlinks/plugins/sentry_flutter/macos
|
||||
share_plus:
|
||||
:path: Flutter/ephemeral/.symlinks/plugins/share_plus/macos
|
||||
shared_preferences_foundation:
|
||||
@ -280,28 +272,28 @@ EXTERNAL SOURCES:
|
||||
|
||||
SPEC CHECKSUMS:
|
||||
bitsdojo_window_macos: 44e3b8fe3dd463820e0321f6256c5b1c16bb6a00
|
||||
connectivity_plus: 4c41c08fc6d7c91f63bc7aec70ffe3730b04f563
|
||||
connectivity_plus: 18382e7311ba19efcaee94442b23b32507b20695
|
||||
croppy: 25a638bd7d05411d8c697f481568f261037694fc
|
||||
cupertino_http: 1d506209e339211efa0764caa7b2de4bc55b6818
|
||||
device_info_plus: 1b14eed9bf95428983aed283a8d51cce3d8c4215
|
||||
file_saver: 44e6fbf666677faf097302460e214e977fdd977b
|
||||
file_selector_macos: cc3858c981fe6889f364731200d6232dac1d812d
|
||||
Firebase: cf1b19f21410b029b6786a54e9764a0cacad3c99
|
||||
firebase_analytics: a80b3d6645f2f12d626fde928b61dae12e5ea2ef
|
||||
firebase_core: e4a35c426636a2cce00a5163df7ba69bfd0cca57
|
||||
firebase_core: 1dfe1f4d02ad78be0277e320aa3d8384cf46231f
|
||||
firebase_messaging: 61f678060b69a7ae1013e3a939ec8e1c56ef6fcf
|
||||
FirebaseAnalytics: 3feef9ae8733c567866342a1000691baaa7cad49
|
||||
FirebaseCore: e0510f1523bc0eb21653cac00792e1e2bd6f1771
|
||||
FirebaseCoreInternal: f47dd28ae7782e6a4738aad3106071a8fe0af604
|
||||
FirebaseCoreInternal: d98ab91e2d80a56d7b246856a8885443b302c0c2
|
||||
FirebaseInstallations: 6ef4a1c7eb2a61ee1f74727d7f6ce2e72acf1414
|
||||
FirebaseMessaging: f8a160d99c2c2e5babbbcc90c4a3e15db036aee2
|
||||
flutter_udid: 6b2b89780c3dfeecf0047bdf93f622d6416b1c07
|
||||
flutter_webrtc: 53c9e1285ab32dfb58afb1e1471416a877e23d7a
|
||||
FlutterMacOS: 8f6f14fa908a6fb3fba0cd85dbd81ec4b251fb24
|
||||
gal: 61e868295d28fe67ffa297fae6dacebf56fd53e1
|
||||
gal: 6a522c75909f1244732d4596d11d6a2f86ff37a5
|
||||
GoogleAppMeasurement: 987769c4ca6b968f2479fbcc9fe3ce34af454b8e
|
||||
GoogleDataTransport: aae35b7ea0c09004c3797d53c8c41f66f219d6a7
|
||||
GoogleUtilities: 26a3abef001b6533cf678d3eb38fd3f614b7872d
|
||||
livekit_client: 8838dc52cb95b452968efb84e87cad895c261d9d
|
||||
livekit_client: 9fdcb22df3de55e6d4b24bdc3b5eb1c0269d774a
|
||||
media_kit_libs_macos_video: b3e2bbec2eef97c285f2b1baa7963c67c753fb82
|
||||
media_kit_native_event_loop: 81fd5b45192b72f8b5b69eaf5b540f45777eb8d5
|
||||
media_kit_video: c75b07f14d59706c775778e4dd47dd027de8d1e5
|
||||
@ -312,8 +304,6 @@ SPEC CHECKSUMS:
|
||||
PromisesObjC: f5707f49cb48b9636751c5b2e7d227e43fba9f47
|
||||
SAMKeychain: 483e1c9f32984d50ca961e26818a534283b4cd5c
|
||||
screen_brightness_macos: 2d6d3af2165592d9a55ffcd95b7550970e41ebda
|
||||
Sentry: e9215d7b17f7902692b4f8700e061e4f853e3521
|
||||
sentry_flutter: 927eed60d66951d1b0f1db37fe94ff5cb7c80231
|
||||
share_plus: 1fa619de8392a4398bfaf176d441853922614e89
|
||||
shared_preferences_foundation: fcdcbc04712aee1108ac7fda236f363274528f78
|
||||
sqflite_darwin: 5a7236e3b501866c1c9befc6771dfd73ffb8702d
|
||||
|
@ -6,4 +6,8 @@ class AppDelegate: FlutterAppDelegate {
|
||||
override func applicationShouldTerminateAfterLastWindowClosed(_ sender: NSApplication) -> Bool {
|
||||
return true
|
||||
}
|
||||
|
||||
override func applicationSupportsSecureRestorableState(_ app: NSApplication) -> Bool {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
@ -10,8 +10,6 @@
|
||||
<true/>
|
||||
<key>com.apple.security.device.audio-input</key>
|
||||
<true/>
|
||||
<key>com.apple.security.device.bluetooth</key>
|
||||
<true/>
|
||||
<key>com.apple.security.device.camera</key>
|
||||
<true/>
|
||||
<key>com.apple.security.files.downloads.read-write</key>
|
||||
|
@ -31,16 +31,18 @@
|
||||
<key>NSPrincipalClass</key>
|
||||
<string>NSApplication</string>
|
||||
<key>NSCameraUsageDescription</key>
|
||||
<string>Grant access to Photo Library will allow Solian take photo or video for your post.</string>
|
||||
<string>Grant access to Camera will allow Solian use your camera during a call.</string>
|
||||
<key>NSMicrophoneUsageDescription</key>
|
||||
<string>Grant access to Photo Library will allow Solian record audio for your post.</string>
|
||||
<string>Grant access to Microphone will allow Solian use your microphone during a call.</string>
|
||||
<key>NSPhotoLibraryAddUsageDescription</key>
|
||||
<string>Grant access to Photo Library will allow Solian download photo to album for you.</string>
|
||||
<key>NSPhotoLibraryUsageDescription</key>
|
||||
<string>Grant access to Photo Library will allow Solian upload photo or video for your post.</string>
|
||||
<key>CFBundleDisplayName</key>
|
||||
<string>$(PRODUCT_NAME)</string>
|
||||
<key>NSCameraUseContinuityCameraDeviceType</key>
|
||||
<string></string>
|
||||
<key>ITSAppUsesNonExemptEncryption</key>
|
||||
<false/>
|
||||
<key>NSPhotoLibraryAddUsageDescription</key>
|
||||
<string>Grant access to Photo Library will allow Solian download photo to album for you.</string>
|
||||
</dict>
|
||||
</plist>
|
||||
|
@ -8,8 +8,6 @@
|
||||
<true/>
|
||||
<key>com.apple.security.device.audio-input</key>
|
||||
<true/>
|
||||
<key>com.apple.security.device.bluetooth</key>
|
||||
<true/>
|
||||
<key>com.apple.security.device.camera</key>
|
||||
<true/>
|
||||
<key>com.apple.security.files.downloads.read-write</key>
|
||||
|
438
pubspec.lock
438
pubspec.lock
@ -5,31 +5,31 @@ packages:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: _fe_analyzer_shared
|
||||
sha256: f256b0c0ba6c7577c15e2e4e114755640a875e885099367bf6e012b19314c834
|
||||
sha256: "16e298750b6d0af7ce8a3ba7c18c69c3785d11b15ec83f6dcd0ad2a0009b3cab"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "72.0.0"
|
||||
version: "76.0.0"
|
||||
_flutterfire_internals:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: _flutterfire_internals
|
||||
sha256: eae3133cbb06de9205899b822e3897fc6a8bc278ad4c944b4ce612689369694b
|
||||
sha256: daa1d780fdecf8af925680c06c86563cdd445deea995d5c9176f1302a2b10bbe
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.3.47"
|
||||
version: "1.3.48"
|
||||
_macros:
|
||||
dependency: transitive
|
||||
description: dart
|
||||
source: sdk
|
||||
version: "0.3.2"
|
||||
version: "0.3.3"
|
||||
analyzer:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: analyzer
|
||||
sha256: b652861553cd3990d8ed361f7979dc6d7053a9ac8843fa73820ab68ce5410139
|
||||
sha256: "1f14db053a8c23e260789e9b0980fa27f2680dd640932cae5e1137cce0e46e1e"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "6.7.0"
|
||||
version: "6.11.0"
|
||||
animations:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
@ -50,10 +50,10 @@ packages:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: archive
|
||||
sha256: cb6a278ef2dbb298455e1a713bda08524a175630ec643a242c399c932a0a1f7d
|
||||
sha256: "6199c74e3db4fbfbd04f66d739e72fe11c8a8957d5f219f1f4482dbde6420b5a"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.6.1"
|
||||
version: "4.0.2"
|
||||
args:
|
||||
dependency: transitive
|
||||
description:
|
||||
@ -122,50 +122,50 @@ packages:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: build
|
||||
sha256: "80184af8b6cb3e5c1c4ec6d8544d27711700bc3e6d2efad04238c7b5290889f0"
|
||||
sha256: cef23f1eda9b57566c81e2133d196f8e3df48f244b317368d65c5943d91148f0
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.4.1"
|
||||
version: "2.4.2"
|
||||
build_config:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: build_config
|
||||
sha256: bf80fcfb46a29945b423bd9aad884590fb1dc69b330a4d4700cac476af1708d1
|
||||
sha256: "4ae2de3e1e67ea270081eaee972e1bd8f027d459f249e0f1186730784c2e7e33"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.1.1"
|
||||
version: "1.1.2"
|
||||
build_daemon:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: build_daemon
|
||||
sha256: "79b2aef6ac2ed00046867ed354c88778c9c0f029df8a20fe10b5436826721ef9"
|
||||
sha256: "294a2edaf4814a378725bfe6358210196f5ea37af89ecd81bfa32960113d4948"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "4.0.2"
|
||||
version: "4.0.3"
|
||||
build_resolvers:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: build_resolvers
|
||||
sha256: "339086358431fa15d7eca8b6a36e5d783728cf025e559b834f4609a1fcfb7b0a"
|
||||
sha256: "99d3980049739a985cf9b21f30881f46db3ebc62c5b8d5e60e27440876b1ba1e"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.4.2"
|
||||
version: "2.4.3"
|
||||
build_runner:
|
||||
dependency: "direct dev"
|
||||
description:
|
||||
name: build_runner
|
||||
sha256: "028819cfb90051c6b5440c7e574d1896f8037e3c96cf17aaeb054c9311cfbf4d"
|
||||
sha256: "74691599a5bc750dc96a6b4bfd48f7d9d66453eab04c7f4063134800d6a5c573"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.4.13"
|
||||
version: "2.4.14"
|
||||
build_runner_core:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: build_runner_core
|
||||
sha256: f8126682b87a7282a339b871298cc12009cb67109cfa1614d6436fb0289193e0
|
||||
sha256: "22e3aa1c80e0ada3722fe5b63fd43d9c8990759d0a2cf489c8c5d7b2bdebc021"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "7.3.2"
|
||||
version: "8.0.0"
|
||||
built_collection:
|
||||
dependency: transitive
|
||||
description:
|
||||
@ -178,10 +178,10 @@ packages:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: built_value
|
||||
sha256: c7913a9737ee4007efedaffc968c049fd0f3d0e49109e778edc10de9426005cb
|
||||
sha256: "28a712df2576b63c6c005c465989a348604960c0958d28be5303ba9baa841ac2"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "8.9.2"
|
||||
version: "8.9.3"
|
||||
cached_network_image:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
@ -258,18 +258,18 @@ packages:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: collection
|
||||
sha256: ee67cb0715911d28db6bf4af1026078bd6f0128b07a5f66fb2ed94ec6783c09a
|
||||
sha256: a1ace0a119f20aabc852d165077c036cd864315bd99b7eaa10a60100341941bf
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.18.0"
|
||||
version: "1.19.0"
|
||||
connectivity_plus:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: connectivity_plus
|
||||
sha256: "876849631b0c7dc20f8b471a2a03142841b482438e3b707955464f5ffca3e4c3"
|
||||
sha256: e0817759ec6d2d8e57eb234e6e57d2173931367a865850c7acea40d4b4f9c27d
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "6.1.0"
|
||||
version: "6.1.1"
|
||||
connectivity_plus_platform_interface:
|
||||
dependency: transitive
|
||||
description:
|
||||
@ -286,14 +286,6 @@ packages:
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.1.2"
|
||||
cronet_http:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: cronet_http
|
||||
sha256: "3af9c4d57bf07ef4b307e77b22be4ad61bea19ee6ff65e62184863f3a09f1415"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.3.2"
|
||||
croppy:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
@ -334,14 +326,6 @@ packages:
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "6.0.0"
|
||||
cupertino_http:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: cupertino_http
|
||||
sha256: "7e75c45a27cc13a886ab0a1e4d8570078397057bd612de9d24fe5df0d9387717"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.5.1"
|
||||
cupertino_icons:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
@ -359,13 +343,13 @@ packages:
|
||||
source: hosted
|
||||
version: "2.3.7"
|
||||
dart_webrtc:
|
||||
dependency: transitive
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: dart_webrtc
|
||||
sha256: c664ad88d5646735753add421ee2118486c100febef5e92b7f59cdbabf6a51f6
|
||||
sha256: e65506edb452148220efab53d8d2f8bb9d827bd8bcd53cf3a3e6df70b27f3d86
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.4.9"
|
||||
version: "1.4.10"
|
||||
dbus:
|
||||
dependency: transitive
|
||||
description:
|
||||
@ -375,21 +359,21 @@ packages:
|
||||
source: hosted
|
||||
version: "0.7.10"
|
||||
device_info_plus:
|
||||
dependency: transitive
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: device_info_plus
|
||||
sha256: f545ffbadee826f26f2e1a0f0cbd667ae9a6011cc0f77c0f8f00a969655e6e95
|
||||
sha256: "4fa68e53e26ab17b70ca39f072c285562cfc1589df5bb1e9295db90f6645f431"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "11.1.1"
|
||||
version: "11.2.0"
|
||||
device_info_plus_platform_interface:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: device_info_plus_platform_interface
|
||||
sha256: "282d3cf731045a2feb66abfe61bbc40870ae50a3ed10a4d3d217556c35c8c2ba"
|
||||
sha256: "0b04e02b30791224b31969eb1b50d723498f402971bff3630bca2ba839bd1ed2"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "7.0.1"
|
||||
version: "7.0.2"
|
||||
dio:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
@ -402,10 +386,10 @@ packages:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: dio_smart_retry
|
||||
sha256: "3d71450c19b4d91ef4c7d726a55a284bfc11eb3634f1f25006cdfab3f8595653"
|
||||
sha256: c8e20da5f49289fa7dce5c9c6b5b120928e3661aefa0fa2d206ea6d93f580928
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "6.0.0"
|
||||
version: "7.0.1"
|
||||
dio_web_adapter:
|
||||
dependency: transitive
|
||||
description:
|
||||
@ -470,6 +454,14 @@ packages:
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.0.7"
|
||||
fading_edge_scrollview:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: fading_edge_scrollview
|
||||
sha256: "1f84fe3ea8e251d00d5735e27502a6a250e4aa3d3b330d3fdcb475af741464ef"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "4.1.1"
|
||||
fake_async:
|
||||
dependency: transitive
|
||||
description:
|
||||
@ -498,10 +490,18 @@ packages:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: file_picker
|
||||
sha256: "16dc141db5a2ccc6520ebb6a2eb5945b1b09e95085c021d9f914f8ded7f1465c"
|
||||
sha256: c2376a6aae82358a9f9ccdd7d1f4006d08faa39a2767cce01031d9f593a8bd3b
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "8.1.4"
|
||||
version: "8.1.6"
|
||||
file_saver:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: file_saver
|
||||
sha256: "017a127de686af2d2fbbd64afea97052d95f2a0f87d19d25b87e097407bf9c1e"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.2.14"
|
||||
file_selector_linux:
|
||||
dependency: transitive
|
||||
description:
|
||||
@ -562,26 +562,26 @@ packages:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: firebase_core
|
||||
sha256: fef81a53ba1ca618def1f8bef4361df07968434e62cb204c1fb90bb880a03da2
|
||||
sha256: "15d761b95dfa2906dfcc31b7fc6fe293188533d1a3ffe78389ba9e69bd7fdbde"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.8.1"
|
||||
version: "3.9.0"
|
||||
firebase_core_platform_interface:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: firebase_core_platform_interface
|
||||
sha256: b94b217e3ad745e784960603d33d99471621ecca151c99c670869b76e50ad2a6
|
||||
sha256: d7253d255ff10f85cfd2adaba9ac17bae878fa3ba577462451163bd9f1d1f0bf
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "5.3.1"
|
||||
version: "5.4.0"
|
||||
firebase_core_web:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: firebase_core_web
|
||||
sha256: "9e69806bb3d905aeec3c1242e0e1475de6ea6d48f456af29d598fb229a2b4e5e"
|
||||
sha256: fbc008cf390d909b823763064b63afefe9f02d8afdb13eb3f485b871afee956b
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.18.2"
|
||||
version: "2.19.0"
|
||||
firebase_messaging:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
@ -627,6 +627,14 @@ packages:
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "4.5.2"
|
||||
flutter_app_update:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: flutter_app_update
|
||||
sha256: "09290240949c4651581cd6fc535e52d019e189e694d6019c56b5a56c2e69ba65"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.2.2"
|
||||
flutter_cache_manager:
|
||||
dependency: transitive
|
||||
description:
|
||||
@ -692,10 +700,10 @@ packages:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: flutter_plugin_android_lifecycle
|
||||
sha256: "9b78450b89f059e96c9ebb355fa6b3df1d6b330436e0b885fb49594c41721398"
|
||||
sha256: "615a505aef59b151b46bbeef55b36ce2b6ed299d160c51d84281946f0aa0ce0e"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.0.23"
|
||||
version: "2.0.24"
|
||||
flutter_shaders:
|
||||
dependency: transitive
|
||||
description:
|
||||
@ -712,6 +720,14 @@ packages:
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.7.0"
|
||||
flutter_svg:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: flutter_svg
|
||||
sha256: "54900a1a1243f3c4a5506d853a2b5c2dbc38d5f27e52a52618a8054401431123"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.0.16"
|
||||
flutter_test:
|
||||
dependency: "direct dev"
|
||||
description: flutter
|
||||
@ -721,10 +737,10 @@ packages:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: flutter_udid
|
||||
sha256: "63384bd96203aaefccfd7137fab642edda18afede12b0e9e1a2c96fe2589fd07"
|
||||
sha256: be464dc5b1fb7ee894f6a32d65c086ca5e177fdcf9375ac08d77495b98150f84
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.0.0"
|
||||
version: "3.0.1"
|
||||
flutter_web_plugins:
|
||||
dependency: "direct main"
|
||||
description: flutter
|
||||
@ -734,10 +750,10 @@ packages:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: flutter_webrtc
|
||||
sha256: "4838217405c42cce422698eacc9c2e17089b9c05322be899c0a725107dcddbdc"
|
||||
sha256: "430859fb5b763d7556d06ef287cfca582e17d9a2dc36da26017f25a5c0b2523e"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.12.3"
|
||||
version: "0.12.4"
|
||||
freezed:
|
||||
dependency: "direct dev"
|
||||
description:
|
||||
@ -766,10 +782,10 @@ packages:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: gal
|
||||
sha256: "54c9b72528efce7c66234f3b6dd01cb0304fd8af8196de15571d7bdddb940977"
|
||||
sha256: "2771519c8b29f784d5e27f4efc2667667eef51c6c47cccaa0435a8fe8aa208e4"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.3.0"
|
||||
version: "2.3.1"
|
||||
gap:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
@ -834,6 +850,14 @@ packages:
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.0.1"
|
||||
home_widget:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: home_widget
|
||||
sha256: b313e3304c0429669fddf1286e1fbf61a64b873f38ba30b3eb890ef0d7560b12
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.7.0"
|
||||
html:
|
||||
dependency: transitive
|
||||
description:
|
||||
@ -854,26 +878,18 @@ packages:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: http_multi_server
|
||||
sha256: "97486f20f9c2f7be8f514851703d0119c3596d14ea63227af6f7a481ef2b2f8b"
|
||||
sha256: aa6199f908078bb1c5efb8d8638d4ae191aac11b311132c3ef48ce352fb52ef8
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.2.1"
|
||||
version: "3.2.2"
|
||||
http_parser:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: http_parser
|
||||
sha256: "2aa08ce0341cc9b354a498388e30986515406668dbcc4f7c950c3e715496693b"
|
||||
sha256: "76d306a1c3afb33fe82e2bbacad62a61f409b5634c915fceb0d799de1a913360"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "4.0.2"
|
||||
http_profile:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: http_profile
|
||||
sha256: "7e679e355b09aaee2ab5010915c932cce3f2d1c11c3b2dc177891687014ffa78"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.1.0"
|
||||
version: "4.1.1"
|
||||
icons_launcher:
|
||||
dependency: "direct dev"
|
||||
description:
|
||||
@ -886,10 +902,10 @@ packages:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: image
|
||||
sha256: f31d52537dc417fdcde36088fdf11d191026fd5e4fae742491ebd40e5a8bea7d
|
||||
sha256: "8346ad4b5173924b5ddddab782fc7d8a6300178c8b1dc427775405a01701c4a6"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "4.3.0"
|
||||
version: "4.5.2"
|
||||
image_picker:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
@ -954,6 +970,22 @@ packages:
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.2.1+1"
|
||||
in_app_review:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: in_app_review
|
||||
sha256: "36a06771b88fb0e79985b15e7f2ac0f1142e903fe72517f3c055d78bc3bc1819"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.0.10"
|
||||
in_app_review_platform_interface:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: in_app_review_platform_interface
|
||||
sha256: fed2c755f2125caa9ae10495a3c163aa7fab5af3585a9c62ef4a6920c5b45f10
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.0.5"
|
||||
intl:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
@ -966,18 +998,10 @@ packages:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: io
|
||||
sha256: "2ec25704aba361659e10e3e5f5d672068d332fc8ac516421d483a11e5cbd061e"
|
||||
sha256: dfd5a80599cf0165756e3181807ed3e77daf6dd4137caaad72d0b7931597650b
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.0.4"
|
||||
jni:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: jni
|
||||
sha256: f377c585ea9c08d48b427dc2e03780af2889d1bb094440da853c6883c1acba4b
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.10.1"
|
||||
version: "1.0.5"
|
||||
js:
|
||||
dependency: transitive
|
||||
description:
|
||||
@ -1006,18 +1030,18 @@ packages:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: leak_tracker
|
||||
sha256: "3f87a60e8c63aecc975dda1ceedbc8f24de75f09e4856ea27daf8958f2f0ce05"
|
||||
sha256: "7bb2830ebd849694d1ec25bf1f44582d6ac531a57a365a803a6034ff751d2d06"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "10.0.5"
|
||||
version: "10.0.7"
|
||||
leak_tracker_flutter_testing:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: leak_tracker_flutter_testing
|
||||
sha256: "932549fb305594d82d7183ecd9fa93463e9914e1b67cacc34bc40906594a1806"
|
||||
sha256: "9491a714cca3667b60b5c420da8217e6de0d1ba7a5ec322fab01758f6998f379"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.0.5"
|
||||
version: "3.0.8"
|
||||
leak_tracker_testing:
|
||||
dependency: transitive
|
||||
description:
|
||||
@ -1038,18 +1062,18 @@ packages:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: lints
|
||||
sha256: "3315600f3fb3b135be672bf4a178c55f274bebe368325ae18462c89ac1e3b413"
|
||||
sha256: c35bb79562d980e9a453fc715854e1ed39e24e7d0297a880ef54e17f9874a9d7
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "5.0.0"
|
||||
version: "5.1.1"
|
||||
livekit_client:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: livekit_client
|
||||
sha256: e1e2524c8b0c9280ab852b5239b9ad00f10f50c82c5f6cc674df3583b6d3408b
|
||||
sha256: "7802b5de1cae2ee3439db730d24d31c6dcbce173c5e6db2fc5774039a290bc2d"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.3.1+hotfix.1"
|
||||
version: "2.3.2"
|
||||
logging:
|
||||
dependency: transitive
|
||||
description:
|
||||
@ -1062,10 +1086,10 @@ packages:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: macros
|
||||
sha256: "0acaed5d6b7eab89f63350bccd82119e6c602df0f391260d0e32b5e23db79536"
|
||||
sha256: "1d9e801cd66f7ea3663c45fc708450db1fa57f988142c64289142c9b7ee80656"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.1.2-main.4"
|
||||
version: "0.1.3-main.0"
|
||||
markdown:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
@ -1074,6 +1098,14 @@ packages:
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "7.2.2"
|
||||
marquee:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: marquee
|
||||
sha256: a87e7e80c5d21434f90ad92add9f820cf68be374b226404fe881d2bba7be0862
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.3.0"
|
||||
matcher:
|
||||
dependency: transitive
|
||||
description:
|
||||
@ -1186,14 +1218,6 @@ packages:
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.0.0"
|
||||
native_dio_adapter:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: native_dio_adapter
|
||||
sha256: "4c925ba15a44478be0eb6e97b62a1c1d07e56b28e566283dbcb15e58418bdaae"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.3.0"
|
||||
nested:
|
||||
dependency: transitive
|
||||
description:
|
||||
@ -1222,26 +1246,26 @@ packages:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: package_config
|
||||
sha256: "1c5b77ccc91e4823a5af61ee74e6b972db1ef98c2ff5a18d3161c982a55448bd"
|
||||
sha256: "92d4488434b520a62570293fbd33bb556c7d49230791c1b4bbd973baf6d2dc67"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.1.0"
|
||||
version: "2.1.1"
|
||||
package_info_plus:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: package_info_plus
|
||||
sha256: da8d9ac8c4b1df253d1a328b7bf01ae77ef132833479ab40763334db13b91cce
|
||||
sha256: "70c421fe9d9cc1a9a7f3b05ae56befd469fe4f8daa3b484823141a55442d858d"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "8.1.1"
|
||||
version: "8.1.2"
|
||||
package_info_plus_platform_interface:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: package_info_plus_platform_interface
|
||||
sha256: ac1f4a4847f1ade8e6a87d1f39f5d7c67490738642e2542f559ec38c37489a66
|
||||
sha256: a5ef9986efc7bf772f2696183a3992615baa76c1ffb1189318dd8803778fb05b
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.0.1"
|
||||
version: "3.0.2"
|
||||
pasteboard:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
@ -1258,6 +1282,14 @@ packages:
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.9.0"
|
||||
path_parsing:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: path_parsing
|
||||
sha256: "883402936929eac138ee0a45da5b0f2c80f89913e6dc3bf77eb65b84b409c6ca"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.1.0"
|
||||
path_provider:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
@ -1270,10 +1302,10 @@ packages:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: path_provider_android
|
||||
sha256: "8c4967f8b7cb46dc914e178daa29813d83ae502e0529d7b0478330616a691ef7"
|
||||
sha256: "4adf4fd5423ec60a29506c76581bc05854c55e3a0b72d35bb28d661c9686edf2"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.2.14"
|
||||
version: "2.2.15"
|
||||
path_provider_foundation:
|
||||
dependency: transitive
|
||||
description:
|
||||
@ -1410,6 +1442,14 @@ packages:
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.3.1"
|
||||
posix:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: posix
|
||||
sha256: a0117dc2167805aa9125b82eee515cc891819bac2f538c83646d355b16f58b9a
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "6.0.1"
|
||||
protobuf:
|
||||
dependency: transitive
|
||||
description:
|
||||
@ -1430,18 +1470,42 @@ packages:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: pub_semver
|
||||
sha256: "40d3ab1bbd474c4c2328c91e3a7df8c6dd629b79ece4c4bd04bee496a224fb0c"
|
||||
sha256: "7b3cfbf654f3edd0c6298ecd5be782ce997ddf0e00531b9464b55245185bbbbd"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.1.4"
|
||||
version: "2.1.5"
|
||||
pubspec_parse:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: pubspec_parse
|
||||
sha256: c799b721d79eb6ee6fa56f00c04b472dcd44a30d258fac2174a6ec57302678f8
|
||||
sha256: "81876843eb50dc2e1e5b151792c9a985c5ed2536914115ed04e9c8528f6647b0"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.3.0"
|
||||
version: "1.4.0"
|
||||
qr:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: qr
|
||||
sha256: "5a1d2586170e172b8a8c8470bbbffd5eb0cd38a66c0d77155ea138d3af3a4445"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.0.2"
|
||||
qr_flutter:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: qr_flutter
|
||||
sha256: "5095f0fc6e3f71d08adef8feccc8cea4f12eec18a2e31c2e8d82cb6019f4b097"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "4.1.0"
|
||||
receive_sharing_intent:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: receive_sharing_intent
|
||||
sha256: ec76056e4d258ad708e76d85591d933678625318e411564dcb9059048ca3a593
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.8.1"
|
||||
relative_time:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
@ -1538,62 +1602,46 @@ packages:
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.3.2"
|
||||
sentry:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: sentry
|
||||
sha256: "2440763ae96fa8fd1bcdfc224f5232e1b7a09af76a72f4e626ee313a261faf6f"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "8.10.1"
|
||||
sentry_flutter:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: sentry_flutter
|
||||
sha256: "3b30038b3b9303540a8b2c8b1c8f0bb93a207f8e4b25691c59d969ddeb4734fd"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "8.10.1"
|
||||
share_plus:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: share_plus
|
||||
sha256: "9c9bafd4060728d7cdb2464c341743adbd79d327cb067ec7afb64583540b47c8"
|
||||
sha256: "6327c3f233729374d0abaafd61f6846115b2a481b4feddd8534211dc10659400"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "10.1.2"
|
||||
version: "10.1.3"
|
||||
share_plus_platform_interface:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: share_plus_platform_interface
|
||||
sha256: c57c0bbfec7142e3a0f55633be504b796af72e60e3c791b44d5a017b985f7a48
|
||||
sha256: cc012a23fc2d479854e6c80150696c4a5f5bb62cb89af4de1c505cf78d0a5d0b
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "5.0.1"
|
||||
version: "5.0.2"
|
||||
shared_preferences:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: shared_preferences
|
||||
sha256: "95f9997ca1fb9799d494d0cb2a780fd7be075818d59f00c43832ed112b158a82"
|
||||
sha256: "3c7e73920c694a436afaf65ab60ce3453d91f84208d761fbd83fc21182134d93"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.3.3"
|
||||
version: "2.3.4"
|
||||
shared_preferences_android:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: shared_preferences_android
|
||||
sha256: "7f172d1b06de5da47b6264c2692ee2ead20bbbc246690427cdb4fc301cd0c549"
|
||||
sha256: "02a7d8a9ef346c9af715811b01fbd8e27845ad2c41148eefd31321471b41863d"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.3.4"
|
||||
version: "2.4.0"
|
||||
shared_preferences_foundation:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: shared_preferences_foundation
|
||||
sha256: "07e050c7cd39bad516f8d64c455f04508d09df104be326d8c02551590a0d513d"
|
||||
sha256: "6a52cfcdaeac77cad8c97b539ff688ccfc458c007b4db12be584fbe5c0e49e03"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.5.3"
|
||||
version: "2.5.4"
|
||||
shared_preferences_linux:
|
||||
dependency: transitive
|
||||
description:
|
||||
@ -1630,10 +1678,10 @@ packages:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: shelf
|
||||
sha256: ad29c505aee705f41a4d8963641f91ac4cee3c8fad5947e033390a7bd8180fa4
|
||||
sha256: e7dd780a7ffb623c57850b33f43309312fc863fb6aa3d276a754bb299839ef12
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.4.1"
|
||||
version: "1.4.2"
|
||||
shelf_web_socket:
|
||||
dependency: transitive
|
||||
description:
|
||||
@ -1646,7 +1694,7 @@ packages:
|
||||
dependency: transitive
|
||||
description: flutter
|
||||
source: sdk
|
||||
version: "0.0.99"
|
||||
version: "0.0.0"
|
||||
sliver_tools:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
@ -1667,10 +1715,10 @@ packages:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: source_helper
|
||||
sha256: "6adebc0006c37dd63fe05bca0a929b99f06402fc95aa35bf36d67f5c06de01fd"
|
||||
sha256: "86d247119aedce8e63f4751bd9626fc9613255935558447569ad42f9f5b48b3c"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.3.4"
|
||||
version: "1.3.5"
|
||||
source_span:
|
||||
dependency: transitive
|
||||
description:
|
||||
@ -1731,10 +1779,10 @@ packages:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: stack_trace
|
||||
sha256: "73713990125a6d93122541237550ee3352a2d84baad52d375a4cad2eb9b7ce0b"
|
||||
sha256: "9f47fd3630d76be3ab26f0ee06d213679aa425996925ff3feffdec504931c377"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.11.1"
|
||||
version: "1.12.0"
|
||||
stream_channel:
|
||||
dependency: transitive
|
||||
description:
|
||||
@ -1747,18 +1795,18 @@ packages:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: stream_transform
|
||||
sha256: "14a00e794c7c11aa145a170587321aedce29769c08d7f58b1d141da75e3b1c6f"
|
||||
sha256: ad47125e588cfd37a9a7f86c7d6356dde8dfe89d071d293f80ca9e9273a33871
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.1.0"
|
||||
version: "2.1.1"
|
||||
string_scanner:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: string_scanner
|
||||
sha256: "556692adab6cfa87322a115640c11f13cb77b3f076ddcc5d6ae3c20242bedcde"
|
||||
sha256: "688af5ed3402a4bde5b3a6c15fd768dbf2621a614950b17f04626c431ab3c4c3"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.2.0"
|
||||
version: "1.3.0"
|
||||
styled_widget:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
@ -1803,18 +1851,18 @@ packages:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: test_api
|
||||
sha256: "5b8a98dafc4d5c4c9c72d8b31ab2b23fc13422348d2997120294d3bac86b4ddb"
|
||||
sha256: "664d3a9a64782fcdeb83ce9c6b39e78fd2971d4e37827b9b06c3aa1edc5e760c"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.7.2"
|
||||
version: "0.7.3"
|
||||
timing:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: timing
|
||||
sha256: "70a3b636575d4163c477e6de42f247a23b315ae20e86442bebe32d3cabf61c32"
|
||||
sha256: "62ee18aca144e4a9f29d212f5a4c6a053be252b895ab14b5821996cff4ed90fe"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.0.1"
|
||||
version: "1.0.2"
|
||||
typed_data:
|
||||
dependency: transitive
|
||||
description:
|
||||
@ -1919,6 +1967,30 @@ packages:
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "4.5.1"
|
||||
vector_graphics:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: vector_graphics
|
||||
sha256: "27d5fefe86fb9aace4a9f8375b56b3c292b64d8c04510df230f849850d912cb7"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.1.15"
|
||||
vector_graphics_codec:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: vector_graphics_codec
|
||||
sha256: "2430b973a4ca3c4dbc9999b62b8c719a160100dcbae5c819bae0cacce32c9cdb"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.1.12"
|
||||
vector_graphics_compiler:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: vector_graphics_compiler
|
||||
sha256: "1b4b9e706a10294258727674a340ae0d6e64a7231980f9f9a3d12e4b42407aad"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.1.16"
|
||||
vector_math:
|
||||
dependency: transitive
|
||||
description:
|
||||
@ -1927,6 +1999,14 @@ packages:
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.1.4"
|
||||
version:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: version
|
||||
sha256: "3d4140128e6ea10d83da32fef2fa4003fccbf6852217bb854845802f04191f94"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.0.2"
|
||||
very_good_infinite_list:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
@ -1939,10 +2019,10 @@ packages:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: vm_service
|
||||
sha256: "5c5f338a667b4c644744b661f309fb8080bb94b18a7e91ef1dbd343bed00ed6d"
|
||||
sha256: f6be3ed8bd01289b34d679c2b62226f63c0e69f9fd2e50a6b3c1c729a961041b
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "14.2.5"
|
||||
version: "14.3.0"
|
||||
volume_controller:
|
||||
dependency: transitive
|
||||
description:
|
||||
@ -1955,26 +2035,26 @@ packages:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: wakelock_plus
|
||||
sha256: bf4ee6f17a2fa373ed3753ad0e602b7603f8c75af006d5b9bdade263928c0484
|
||||
sha256: "36c88af0b930121941345306d259ec4cc4ecca3b151c02e3a9e71aede83c615e"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.2.8"
|
||||
version: "1.2.10"
|
||||
wakelock_plus_platform_interface:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: wakelock_plus_platform_interface
|
||||
sha256: "422d1cdbb448079a8a62a5a770b69baa489f8f7ca21aef47800c726d404f9d16"
|
||||
sha256: "70e780bc99796e1db82fe764b1e7dcb89a86f1e5b3afb1db354de50f2e41eb7a"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.2.1"
|
||||
version: "1.2.2"
|
||||
watcher:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: watcher
|
||||
sha256: "3d2ad6751b3c16cf07c7fca317a1413b3f26530319181b37e3b9039b84fc01d8"
|
||||
sha256: "69da27e49efa56a15f8afe8f4438c4ec02eff0a117df1b22ea4aad194fe1c104"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.1.0"
|
||||
version: "1.1.1"
|
||||
web:
|
||||
dependency: transitive
|
||||
description:
|
||||
@ -2023,6 +2103,14 @@ packages:
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.1.5"
|
||||
workmanager:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: workmanager
|
||||
sha256: ed13530cccd28c5c9959ad42d657cd0666274ca74c56dea0ca183ddd527d3a00
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.5.2"
|
||||
xdg_directories:
|
||||
dependency: transitive
|
||||
description:
|
||||
@ -2043,10 +2131,10 @@ packages:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: yaml
|
||||
sha256: "75769501ea3489fca56601ff33454fe45507ea3bfb014161abc3b43ae25989d5"
|
||||
sha256: b9da305ac7c39faa3f030eccd175340f968459dae4af175130b3fc47e40d76ce
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.1.2"
|
||||
version: "3.1.3"
|
||||
sdks:
|
||||
dart: ">=3.5.4 <4.0.0"
|
||||
dart: ">=3.6.0 <4.0.0"
|
||||
flutter: ">=3.24.0"
|
||||
|
23
pubspec.yaml
23
pubspec.yaml
@ -1,5 +1,5 @@
|
||||
name: surface
|
||||
description: "A new Flutter project."
|
||||
description: "The application for Solar Network"
|
||||
# The following line prevents the package from being accidentally published to
|
||||
# pub.dev using `flutter pub publish`. This is preferred for private packages.
|
||||
publish_to: "none" # Remove this line if you wish to publish to pub.dev
|
||||
@ -16,7 +16,7 @@ publish_to: "none" # Remove this line if you wish to publish to pub.dev
|
||||
# https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html
|
||||
# In Windows, build-name is used as the major, minor, and patch parts
|
||||
# of the product and file versions while build-number is used as the build suffix.
|
||||
version: 2.0.0+20
|
||||
version: 2.1.1+35
|
||||
|
||||
environment:
|
||||
sdk: ^3.5.4
|
||||
@ -45,8 +45,7 @@ dependencies:
|
||||
responsive_framework: ^1.5.1
|
||||
animations: ^2.0.11
|
||||
dio: ^5.7.0
|
||||
native_dio_adapter: ^1.3.0
|
||||
dio_smart_retry: ^6.0.0
|
||||
dio_smart_retry: ^7.0.1
|
||||
very_good_infinite_list: ^0.9.0
|
||||
freezed_annotation: ^2.4.4
|
||||
json_annotation: ^4.9.0
|
||||
@ -62,7 +61,7 @@ dependencies:
|
||||
relative_time: ^5.0.0
|
||||
image_picker: ^1.1.2
|
||||
cross_file: ^0.3.4+2
|
||||
file_picker: ^8.1.3
|
||||
file_picker: ^8.1.6 # pinned due to compile failed on android, https://github.com/miguelpruivo/flutter_file_picker/issues/1643
|
||||
croppy: ^1.3.1
|
||||
flutter_expandable_fab: ^2.3.0
|
||||
dropdown_button2: ^2.3.9
|
||||
@ -86,8 +85,8 @@ dependencies:
|
||||
media_kit_video: ^1.2.5
|
||||
media_kit_libs_video: ^1.0.5
|
||||
pasteboard: ^0.3.0
|
||||
sentry_flutter: ^8.10.1
|
||||
synchronized: ^3.3.0+3
|
||||
dart_webrtc: ^1.4.10
|
||||
livekit_client: ^2.3.1+hotfix.1
|
||||
wakelock_plus: ^1.2.8
|
||||
permission_handler: ^11.3.1
|
||||
@ -101,6 +100,17 @@ dependencies:
|
||||
package_info_plus: ^8.1.1
|
||||
intl: ^0.19.0
|
||||
screenshot: ^3.0.0
|
||||
qr_flutter: ^4.1.0
|
||||
file_saver: ^0.2.14
|
||||
device_info_plus: ^11.2.0
|
||||
marquee: ^2.3.0
|
||||
flutter_svg: ^2.0.16
|
||||
home_widget: ^0.7.0
|
||||
receive_sharing_intent: ^1.8.1
|
||||
workmanager: ^0.5.2
|
||||
flutter_app_update: ^3.2.2
|
||||
in_app_review: ^2.0.10
|
||||
version: ^3.0.2
|
||||
|
||||
dev_dependencies:
|
||||
flutter_test:
|
||||
@ -133,6 +143,7 @@ flutter:
|
||||
# To add assets to your application, add an assets section, like this:
|
||||
assets:
|
||||
- assets/icon/icon.png
|
||||
- assets/icon/icon-dark.png
|
||||
- assets/icon/icon-light-radius.png
|
||||
- assets/translations/
|
||||
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user